setup函数
- 与data、methods中的数据合并时,setup返回的数据的优先级更高
- 没有this,setup执行在beforeCreate之前
- 参数1,props
- 参数2,context对象,包括arrtrs,slots,emit,expose
expose 函数
-
expose
函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用(组件上的ref)访问该组件的实例时,将仅能访问expose
函数暴露出的内容
ref
- 会将传入的数据(基本类型或者复杂类型)包装成对象,在script中修改时要使用.value,在模板中不需要.value(模板渲染上下文的顶层属性时才适用自动“解包”)
unref
- 如果参数是 ref,则返回内部值,否则返回参数本身。
shallowRef
- 只有重新赋值才是响应式
reactive
- 只接受对象类型的参数
- script中不需要使用.value
shallReactive
- 只有对象的第一层属性是响应式的
toRefs
- 将响应式对象中的所有属性包装为ref对象并返回
- setup返回reactive定义的对象时,如果使用...展开对象,则对象中基本类型失去响应性,可以使用...toRefs(obj),使每个属性变为响应性
toRef
- 为响应性对象上的某个属性创建一个ref引用,属性更新时,引用对象会同步更新
readonly
- 只读代理对象,不可修改
shallowReadonly
- 修改第一层属性会报错
toRow
- 返回由
reactive()
、readonly()
、shallowReactive()
或者shallowReadonly()
创建的代理对应的原始对象 - 返回的数据不具有响应性
markRow
- 将一个对象标记为不可被转为代理。返回该对象本身。
computed函数
- 接收一个包含返回值的回调函数,或者包含getter/setter函数的对象(可写计算属性)
- getter 不应有副作用,应该使用监听器根据其他响应式状态的变更来创建副作用
- 计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算
- 返回值为一个计算属性 ref,与ref对象表现一致
- 计算属性值会基于其响应式依赖被缓存
watch函数
- 监听一个、多个、getter函数等,监听多个时第一个使用数组参数
- 监听reactive对象的属性时,第一个参数必须使用函数形式(也就是用一个返回该属性的 getter 函数)
watchEffect
- 初始化时就执行一次,不需要指定监听的数据
provide函数
- provide('color','red')
inject函数
- color = inject('color')
响应性数据的判断
- isRef
- isReactive
- isReadonly
- isProxy
生命周期
- onBeforeMount
- onMounted
- onBeforeUpdate
- onUpdated
- onBeforeUnmount
- onUnmounted
- onErrorCaptured
- onRenderTracked
- onRenderTriggered
- onActivated
- onDeactivated
组件
- Fragment,允许多个根元素
- Suspense,展示中间过渡状态
- TransitionGroup,添加动画效果
css伪类
- :deep
- :slotted
- :global
css v-bind
- 单文件组件的 <style> 标签支持使用 v-bind CSS 函数将 CSS 的值链接到动态的组件状态:
<script setup>
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
获取dom
<input ref = inputDom />
const inputDom = ref(null);
onMounted(() => {
nextTick(() => {
inputDom.value.focus()
})
})
customRef
- 使用customRef,写一个组合式函数,实现自动依赖追踪,触发响应
import { customRef } from 'vue'
export function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
<script setup>
import { useDebouncedRef } from './debouncedRef'
const text = useDebouncedRef('hello')
</script>
<template>
<input v-model="text" />
</template>
Vue 的两个核心功能
- 声明式渲染:Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。
- 响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM。
API风格怎么选
- 当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐采用选项式 API。
- 当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API + 单文件组件。
根组件模板选择
当根组件没有设置 template 选项时,Vue 将自动使用容器的 innerHTML 作为模板。
响应性丢失
- 不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失
let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({ count: 1 })
- 将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性
const state = reactive({ count: 0 })
// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++
// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count)
ref保持响应性
- 一个包含对象类型值的 ref 可以响应式地替换整个对象
const objectRef = ref({ count: 0 })
// 这是响应式的替换
objectRef.value = { count: 1 }
- ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性
const obj = {
foo: ref(1),
bar: ref(2)
}
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)
// 仍然是响应式的
const { foo, bar } = obj
数组和集合类型的 ref 解包
- 跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
关于v-for中的key
- 推荐在任何可行的时候为 v-for 提供一个 key attribute,除非所迭代的 DOM 内容非常简单 (例如:不包含组件或有状态的 DOM 元素),或者你想有意采用默认行为来提高性能。
- key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为 v-for 的 key。
计算属性中操作数组注意事项
- 在计算属性中使用 reverse() 和 sort() 的时候务必小心!这两个方法将变更原始数组,计算函数中不应该这么做
- return numbers.reverse() // bad
+ return [...numbers].reverse() //good
watch vs. watchEffect
- watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
- watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
监听器回调的触发时机
- 默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用,意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
- 如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项
停止侦听器
- 侦听器会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止,所以侦听器必须用同步语句创建。
- 如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。
函数模板引用
- ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">
组件ref
- 如果一个子组件使用的是选项式 API 或没有使用 <script setup>,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权
- 使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露
使用props & emit
- setup标签中必须在组件的 props 列表上声明它。这里要用到
defineProps
宏和definEmits宏
<!-- BlogPost.vue -->
<script setup>
const props = defineProps(['title'])
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>
<template>
<h4>{{ title }}</h4>
</template>
- defineProps 是一个仅 <script setup> 中可用的编译宏命令,并不需要显式地导入。声明的 props 会自动暴露给模板。defineProps 会返回一个对象,其中包含了可以传递给组件的所有 props
- 如果你没有使用 <script setup>,props 必须以 props 选项的方式声明,props 对象会作为 setup() 函数的第一个参数被传入
export default {
props: ['title'],
emits: ['enlarge-text'],
setup(props,ctx) {
console.log(props.title);
ctx.emit('enlarge-text')
}
}
全局注册组件的问题
- 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)
- 全局注册在大型项目中使项目的依赖关系变得不那么明确,这可能会影响应用长期的可维护性
使用一个对象绑定多个 prop
const post = {
id: 1,
title: 'My Journey with Vue'
}
<BlogPost v-bind="post" />
// 上面写法等价于下面的写法
<BlogPost :id="post.id" :title="post.title" />
透传attributes时注意
- 虽然 attrs 对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。你不能通过侦听器去监听它的变化。如果你需要响应性,可以使用 prop。或者你也可以使用 onUpdated() 使得在每次更新时结合最新的 attrs 执行副作用。
无渲染组件
- 一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件
<MouseTracker v-slot="{ x, y }">
Mouse is at: {{ x }}, {{ y }}
</MouseTracker>
组合式函数
- 组合式函数名以“use”开头,例如:useMouse
- 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
- 你可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数
- 有状态逻辑,负责管理会随时间而变化的状态,例如:跟随鼠标位置
- 无状态的逻辑,例如:lodash、date-fns
- 相比无渲染组件,组合式函数不会产生额外的组件实例开销
- 推荐在纯逻辑复用时使用组合式函数,在需要同时复用逻辑和视图布局时使用无渲染组件。
自定义指令
- 简化形式:一个很常见的情况是仅仅需要在 mounted 和 updated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令,如下所示:
<div v-color="color"></div>
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
Vue 自身的安全机制
- HTML 内容转译
// userProvidedString
<h1>{{ userProvidedString }}</h1>
- Attribute 绑定转译
// 动态 attribute 的绑定会被自动转义
<h1 :title="userProvidedString">
hello
</h1>
潜在的危险
- HTML注入:v-html、使用渲染函数、以JSX的形式使用渲染函数
- URL 注入:使用第三方库、后端在保存至数据库之前处理
<a :href="userProvidedUrl">
click me
</a>
- 样式注入
// bad,恶意用户仍然能利用 CSS 进行“点击劫持”,例如,可以在“登录”按钮上方覆盖一个透明的链接
<a
:style="userProvidedStyles"
>
click me
</a>
// good,给用户提供特定的属性名
<a
:style="{
color: userProvidedColor,
background: userProvidedBackground
}"
>
click me
</a>
- JavaScript 注入
我们强烈建议任何时候都不要在 Vue 中渲染 <script>
vue构建各种应用
集成其他库
- 集成Immer,使用不可变数据
- 集成XState,使用状态机
- 集成RxJS,处理异步事件流
响应性语法糖
配置打开支持响应性语法糖
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
- 响应性变量
实验性的功能,不需要.value即可修改数据,是因为可以编译时转换
// 这些宏函数都是全局可用的、无需手动导入。但如果你想让它更明显,你也可以选择从 vue/macros 中引入它们
import { $ref } from 'vue/macros'
let count = $ref(3) // 3
-
ref
->$ref
-
computed
->$computed
-
shallowRef
->$shallowRef
-
customRef
->$customRef
-
toRef
->$toRef
- 通过 $() 解构
import { useMouse } from '@vueuse/core'
const { x, y } = $(useMouse())
console.log(x, y) // 434 349
- 对 $() 的解构在响应式对象和包含数个 ref 的对象都可用。
- 用 $() 将现存的 ref 转换为响应式对象
function myCreateRef() {
return ref(0)
}
let count = $(myCreateRef())
- 响应式 props 解构
const { msg, foo: bar,count=1 } = definProps()
- 保持在函数间传递时的响应性
// 使用$$对函数的参数进行包装
let count = $ref(0)
trackChange($$(count))
- 作为函数返回值
// 如果将响应式变量直接放在返回值表达式中会丢失掉响应性:
function useMouse() {
let x = $ref(0)
let y = $ref(0)
// 监听 mousemove 事件
// 不起效!
return {
x,
y
}
// 修改后起效
return $$({
x,
y
})
}
- 在已解构的 props 上使用 $$()
const { count } = defineProps<{ count: number }>()
passAsRef($$(count))
VueRouter.createWebHashHistory()
- 应用hash路由模式
VueRouter.createWebHsitory()
- 应用路由history模式
useRouter 或 useRoute
- 可以在 setup 函数中访问路由
Sensitive 与 strict 路由配置
- 默认情况下,路由 /users 将匹配 /users、/users/、甚至 /Users/。这种行为可以通过 strict 和 sensitive 选项来修改,它们可以既可以应用在整个全局路由上,又可以应用于当前路由上:
- sensitive,使路由匹配区分大小写
- strict,严格检查路径末尾是否有尾部斜线(/)
可选参数
const routes = [
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /users 和 /users/42
{ path: '/users/:userId(\\d+)?' },
]
全局解析守卫
- 在 每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
动态路由
- router.addRoute()
router.addRoute({ path: '/about', component: About })
// 我们也可以使用 this.$route 或 route = useRoute() (在 setup 中)
router.replace(router.currentRoute.value.fullPath)
// 添加嵌套路由
router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', component: AdminSettings })
- router.removeRoute()
router.addRoute({ path: '/about', name: 'about', component: About })
// 删除路由
router.removeRoute('about')
pinia api
- defineStore,定义一个store
在pinia中使用选项式api
- pinia的store中也可以使用state、getters、actions选项,类似vuex
- 它也提供类似的辅助函数mapStores()、mapState() 或 mapActions()
pinia中定义 store
- 你可以定义任意多的 store,但为了让使用 pinia 的益处最大化(比如允许构建工具自动进行代码分割以及 TypeScript 推断),你应该在不同的文件中去定义 store。
解构store
- store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value,就像 setup 中的 props 一样,如果你写了,我们也不能解构它,但你可以直接从 store 中解构 action。你可以使用storeToRefs来解决解构state和getter时丢失响应性的问题
访问state
- 默认情况下,你可以通过 store 实例访问 state,直接对其进行读写。
const store = useStore()
store.count++
重置state
- 你可以通过调用 store 的 $reset() 方法将 state 重置为初始值。
const store = useStore()
store.$reset()
变更 state
- 除了用 store.count++ 直接改变 store,你还可以调用 $patch 方法。
- $patch方法可以接受对象语法或者一个回调函数
替换state
- 你不能完全替换掉 store 的 state,因为那样会破坏其响应性。但是,你可以 patch 它。
订阅state
<script setup>
const someStore = useSomeStore()
// this subscription will be kept even after the component is unmounted
someStore.$subscribe(callback, { detached: true })
</script>
订阅action
<script setup>
const someStore = useSomeStore()
// this subscription will be kept even after the component is unmounted
someStore.$onAction(callback, true)
</script>