全局配置
app
暴露了一些可以全局使用的属性
app.config.errorHandler
可以捕获子组件中未被捕获的异常(这在前端异常监控中很有用 就像原生的 window.addEventListener('error', callback, true) | window.addEventListener('unhandledRejection', callback)
)
1 | app.config.errorHandler = (err) => { //处理错误 } |
app.config.globalProperties
可以显式配置一些全局可用的属性(比如附加在window
上的自定义属性啥的 模板内的表达式不能访问没有显式声明的全局属性)
1 | app.config.globalProperties.window.ownPro = 'own' |
app.component
可以注册一些全局可用的资源 比如使用其注册一个全局可用的组件 可以链接调用
1 | app.component('globalComponent', globalComponent) |
所有这些配置都要在应用挂载前配置好(before
app.mount
)
多个应用实例
可以使用 多个createVue
创建多个 vue 实例 这在我们只想要用 vue 去实现应用的部分功能时很有用比如说使用 vue 来增强服务端渲染SSR
(我们可以将多个实例分别挂载在对应的部分上 而不是全部挂载在同一个实例上)
1 | const app1 = createVue(...); app1.mount('route1') const app2 = createVue(...); |
模板语法
如果使用 jsx 在编译阶段不会像
template
一样被底层优化(vue 在编译阶段会将template
模板转换为 ast 在将 ast 转换为对应的render
渲染函数)
v-html可以将渲染html
内容而不是转换为字符串(不能使用 v-html
来拼接组合模板 因为 vue 不是依靠字符串来解析模板的)
1 | <span v-html="<h1>this is html</h1>"></span> |
v-html
具有很大的安全隐患 容易造成xss
攻击 应该在确保绝对安全的情况下再使用
动态绑定多个值
可以通过 v-bind
不带参数来动态绑定多个值
1 | const data = { name: 'james', age: 23 } |
绑定的表达式中的方法会在组件每次更新时被调用 所以这个方法不应该有副作用(比如改变数据或者触发一异步操作)
1 <Compo :func="funcWithoutEffect(agrs)"></Compo>
动态参数
1 | const propName = 'href' |
动态参数的值只能是字符串或者 null
(null
表示移除这个attribute
)
建议使用计算属性来返回动态参数
响应式
当响应式状态(
reactive state
)被更新时DOM
并不会立马做更新操作 而是会缓存起来 等到下一次更新时一起更新(比如期间改变一百次 最后只会执行一次更新操作)如果想在state
改变后立马用改变后的值进行某些操作 可以使用nextTick()
1
2
3
4
5
6
7
8 <script setup>
import { reactive, nextTick } from "vue";
const data = reactive({ name: "james" });
const change = () => {
data.name = "curry";
nextTick(() => {});
};
</script>
响应式默认是深度响应(如果想要创建只有根部具有响应性的浅度响应对象 可以使用 shalldowReactive
)
响应式代理
响应式代理的本质是返回该对象的proxy
(vue2 使用definePropertype
vue3 使用proxy
)这意味着如果对一个对象使用reactive()
则返回其本身的代理 对该对象进行任何改变都没有响应式 对代理对象才有 对一个已经被reactive()
的对象再次进行 reactive()
返回该对象本身
1 | //同一个对象使用reactive会返回相同对象 |
我们必须保持对响应性对象的相同引用才能够保持其响应性(这就是为啥解构会使其失去响应性)
1
2
3
4 const data = reactive({
name: "james",
});
const { name } = data; //失去了响应性可以使用
toRefs()
来使其保持响应性
ref()
可以在被传递/解构时保持响应性 这使得他经常用于组合函数中(提高代码逻辑的复用性)
1 | const name = ref("james"); |
当一个ref
被当成对象的属性时 会被自动解包
1 | const conunt = ref(0); |
将一个新的 ref 赋值给对象的属性 则旧的 ref 会被替换
当 ref 作为数组或者 map 的元素时 不会自动解包(map.get(key).value
)
计算属性
vue 的计算属性会自动追踪响应式依赖(计算属性会基于其以来进行缓存 直到依赖发生改变时再重新计算)
computed
的getter
不应该有副作用的操作(异步请求数据或者改变 dom)- 永远不要去改变 computed 返回的值 把它当成一个可读不可写的数据 如果需要更改该数据 应该去改变其依赖的响应式数据
类与 style 绑定
可以给类绑定一个计算属性 这很有用
1 | const isActive = ref(true); |
在组件上使用
对于只有一个根元素的组件 当你是用了 class
时 这些class
会被添加到根元素上 并且与该元素上已有的class
合并
1 | //MyComponent.vue |
自动前缀
当在 :style
中是用了需要浏览器前缀的 CSS 属性时 vue
绘自动加上对应的前缀(polyfill
)
如果给一个样式属性提供多个不同的前缀值 则只会渲染浏览器支持的最后一个值(在支持不需要特别前缀的浏览器中都会渲染为 display:flex
)
条件渲染
v-if
和 v-show
v-if
在切换时 组件会进行重新创建和销毁v-if
是惰性的 初次渲染是false
时不会做任何事 等到被改为true
才会被渲染v-show
则无论初始条件是啥都会被渲染 只有display
属性会被切换- 如果需要频繁切换 则使用
v-show
否则使用v-if
v-if
优先级高于v-for
可以在外层新包装一层
<template>
再在上面使用v-for
来解决这个问题
1
2
3
4
5 <template v-for="todo in todos" :key="todo.id">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
列表渲染
vue 可以监听响应式数组的变更方法并在这些方法被调用的时候更新示图
push | pop | shift | unshift | splice | sort | reverse
当你需要展示过滤/排序后的结果时又不想改变原始数据时 可以创建返回已经处理好的数组的计算属性
1 | const numbers = ref([1, 2, 3, 4, 5]) const evenNumbers = computed(() => { return |
在计算属性中使用会改变原数组的方法(比如
reverse | sort
)时 应该创建一个副本进行操作
1
2
3 - return numbers.reverse()
+ return [...numbers].reverse()
事件处理
在内联事件处理器中访问事件参数
应该传入 $event
或者使用内联箭头函数
1 | <!-- 使用特殊的 $event 变量 --> |
事件修饰符
.(stop | prevent | self | capture | once | passive)
1 | <!-- 单击事件将停止传递 --> |
.passive
一般用于触摸事件的监听器 可以用来改善移动端的滚屏性能不要同时使用
.passive
和.prevent
因为.passive
表示不阻止事件的默认行为 而.prevent
表示阻止事件的默认行为
.exact
允许控制触发一个事件所需的确定组合的系统按键修饰符
表单输入绑定
修饰符
默认情况下 v-model
会在每次 input
事件后更新数据 可以添加 .lazy
修饰符改为每次 change
事件后更新数据
生命周期
watch
watch
的第一个参数可以是 ref(包括计算属性)、响应式对象、getter、多个数组源的数组
不可以直接监听响应式对象的属性值 应该监听其
getter
1
2
3
4
5
6
7
8
9
10
11
12
13
14 const obj = reactive({
count: 0,
});
//不要直接监听属性
watch(obj.count, (count) => {
//...
});
//应该直接监听getter
watch(
() => obj.count,
() => {
//...
}
);
watch
监听响应式对象时会隐式创建一个深层监听器 如果监听的是getter
则只有在返回不同的对象时才会触发回调(可以使用 {deep: true}
强制深层监听)
watchEffect()
watchEffect
会立即执行一遍回调函数 如果这时函数产生了副作用 则 Vue 会自动追踪副作用的依赖关系并自动分析出响应源(watch
是惰性的 只有在依赖源发生改变时才会触发)
1 | //比如我们需要立即获取数据后并更具url改变来重新获取数据 |
watchEffect
尽在同步执行期间才追踪依赖 在异步回调时 只有在第一个await
正常工作前访问到的属性才会被追踪
watch
vs watchEffect
watch
值追踪明确侦听的数据源watch
避免在发生副作用期间追踪依赖 可以更加精确地控制回调函数的触发时机watchEffect
会在副作用发生期间追踪依赖 会在同步执行过程中自动追踪到所有能访问到的响应式依赖 但是依赖关系不明确
执行时机
监听器的回调执行会在vue
更新之前被调用 所以意味着我们在回到中访问到的DOM
是vue
更新之前的状态
如果想要在访问更新之后的 DOM 可以指明 flush: 'post'
或者使用 watchPostEffect
1 | watch(source, callback, { |
销毁监听器
正常通过同步语句创建的监听器会在宿主组件卸载时自动停止
如果是通过异步的方式创建(比如在 setTimeout
中创建) 就必须手动停止防止内存泄漏
1 | const unwatch = watch(source, callback); |
模板引用
需要创建一个同名的ref
来进行引用
1 | <script setup> |
只有在组件挂载后才可以访问模板引用 这意味着如果想要侦听一个模板引用ref
的变化 需要考虑null
1 | watchEffect(() => { |
v-for 中的模板引用
在v-for
中使用模板引用时 对应的ref
是一个数组 将在元素被挂载后包含对应整个列表中的所有元素
1 | <script setup> |
深入组件
可以使用 app.component
全局注册组件 (可以链式调用)
全局注册的缺点
- 没有被使用到的组件无法在打包时被
three-shaking
- 依赖关系不明确 不利于维护
props
需要使用 defineProps
显式声明接收的props
1 | defineProps<{ |
可以使用没有参数的 v-bind
一次性传递所有的props
v-bind="post" === :id="post.id" :title="post.title"
如果需要对传入的prop
值做进一步的转换 建议使用计算属性
1 | const props = defineProps(["size"]); |
除了props
的类型时Boolean
(默认是false
) 其他都是 undefined
组件事件
触发和监听事件
可以通过在子组件使用 $emit
定义一个事件并在父组件通过 v-on | @
来触发
1 | <button @click="$emit('someEvent')"> |
组件触发的事件没有 冒泡机制 所以你只能监听直接子组件 而不能监听后代组件或者兄弟组件 如果需要监听其他类型的组件 可以考虑使用
eventBus
声明要触发的事件
可以通过 defineEmits
显式声明要触发的事件
1 | const emits = defineEmits(['infocus']); |
事件校验
通过返回一个布尔值来表明事件是否合法
1 | <script setup> |
如果一个原生事件的名字(比如
click
)被定义在emits
选项中 那么监听器只会监听组件触发的事件而不会响应原生的事件
配合 v-model 使用
1 | <input v-model="data" /> |
参数修饰符
对于又有参数又有修饰符的v-model
生成的prop
是 arg + Modifiers
1 | <MyComponent v-model.capitalize="myText" /> |
透传 Attributes
传递给一个组件 却没有被该组件声明为props
或者emits
的attribute
或者 v-on
事件监听器 将被自动添加到子元素的根元素上
v-on 监听器继承
1 | <MyButton @click="onClick" /> |
深层组件继承
如果被defineProps | defineEmits
声明过 则认为已经被该组件消费 否则将继续向下透传
禁用 Attributes 继承
如果不想要自动继承attributes
可以额外声明一个 script
来声明 inheritAttrs: false
1 | <script> |
如果 attribute 需要应用在根节点以外的其他元素上 通过设置 inheriAttrs: false
并且通过 $attrs
即可访问到除了被声明的props
和emits
之外的所有其他attributes
1 | //例如 想要父元素的所有属性都作为attribute应用到目标上 可以使用没有参数的v-bind |
多个根节点的透传
如果是多个根节点 则不会发生自动透传 需要显式指定 $attrs
否则会报错
可以使用 useAttrs()
来访问一个组件的所有透传 attribute
Solts
渲染作用域
由于插槽是在父组件定义的 所以插槽能访问父组件的数据 但是无法访问子组件的数据
默认值
使用 <slot>defaultData<slot>
给插槽提供默认值并在有填充值时被取代
具名插槽
当子组件有多个插槽位置时 就需要 指定对应的插槽名(默认为default
)
1 | <BaseLayout> |
作用域插槽
如果想要同时访问子组件和父组件的数据 则需要使用作用域插槽将子组件的对应数据暴露出去 然后父组件使用 slotProps
接收
1 | <slot class="class"> |
同样也可以使用 具名作用域插槽 v-slot:name = "slotProps"
如果混用了具名插槽和默认插槽 则需要为默认插槽使用显式的 <template>
标签
1 | <!-- 该模板无法编译 --> |
为默认的插槽使用显式的
<template>
标签可以更清晰地标注插槽数据的使用范围
1
2
3
4
5
6
7
8
9
10
11
12
13 <template>
<MyComponent>
<!-- 使用显式的默认插槽 -->
<template #default="{ message }">
<p>{{ message }}</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</MyComponent>
</template>
用途
可以用来做无渲染组件(只负责逻辑编写 然后将结果通过 prop 传出去 而样式以及数据的使用则交给父组件)
在需要同时服装逻辑 组合视图界面很有用(比如列表)
依赖注入(provide | inject)
应用层 provide
可以通过 app.provide(key, value)
来提供全局可用的数据 这在编写插件时特别有用 因为插件一般不会使用组件形式来提供值
inject
如果provide
的是一个ref
则会传递整个ref
而不是自动解包(这样可以保持响应性)
为了避免在用不到默认值的情况下进行不必要的计算或者产生副作用 可以使用工厂函数来创建默认值
1 | const value = inject("key", () => new ExpensiveClass()); |
和响应式数据配合使用
为了数据的管理 我们应该避免在inject
组件内对provide
的响应式数据进行更改 如果有需要 应该在provide
组件定义改变数据的方法并provide
对于一些不能在inject
组件里更改的数据 可以使用readonly
来保护 provide('read-only-count', readonly(count))
命名冲突
为了避免命名冲突 可以用一个单独的文件管理provide
的key
使用Symbol()
修饰
export const myInjectionKey = Symbol()
异步组件
逻辑复用
组合式函数
可以将重复使用的逻辑封装复用
1 | //mouse.js |
每一个调用
useMouse()
的组件示例会创建独立的状态 不会互相影响
异步组件获取数据
1 | //useFetch |
约定和最佳实践
输入参数
如果你的参数是响应性并且可能被其他开发者使用 则应该使用 unref
兼容ref
而不是原始的值(unref
会在是ref
的时候返回..value
在不是的时候返回原样 )
1 | import { unref } from "vue"; |
如果组合式函数在接收ref
为参数式会产生响应式effect
去额宝使用watch
显式监听此effect
或者在watchEffect
中用unref
进行正确的追踪
返回结果
建议使用多个ref
构成的非响应式对象 如果非要使用响应式对象 建议用reactive
在外面包一层 reactive(useMouse())
副作用
- 如果用到了服务端渲染 确保在组件挂载后才调用的生命周期钩子中执行
DOM
相关的副作用 - 确保在
onUnmounted()
时清理副作用
组合式函数应该始终被同步调用
和无渲染组件的相比
组合式函数不会产生额外的组件实例开销 当在整个应用中使用时 由无渲染组件产生的额外组件实例会带来无法忽视的性能开销
在纯逻辑复用时使用组合式函数 在需要同时复用逻辑和视图布局时使用无渲染组件
自定义指令
自定义组件主要是为了重用涉及普通元素的底层DOM
访问的逻辑
1 | <script setup> |
当所需功能只能通过直接的 DOM 操作来实现时 才使用自定义指令 其他情况应该尽可能使用
v-bind
这样更高效 也对服务端渲染更好
不推荐在组件上使用自定义指令 因为指令默认会传递给组件的根节点上 且不能像attribute
那样通过 $attrs
来绑定
插件
插件是一个拥有 install()
方法的对象 或者直接是一个install()
函数本身
1 | const myPlugin = { |
插件的应用场景
- 通过
app.component() && app.directive()
注册一到多个全局组件或者自定义指令 - 通过
app.provide()
使一注入全局资源 - 向
app.config.globalProperties
中添加一些全局实例属性或方法 - 一个可能上诉三种都包含的功能库(
vue-router
)
编写一个插件
1 | export default { |
可以在插件中provide
这样在整个应用内部就都能使用了
Transition
<Transition>
会在一个元素或组件进入和离开DOM
时应用动画
1 | <button @click="show = !show">Toggle</button> |
<Transition>
仅支持单个元素或组件作为其插槽内容 如果内容是一个组件 这个组件必须仅有一个根元素
当一个 <Transition>
组件中的元素被插入或者移除时
- Vue 会自动检测目标元素是否用了 CSS 过渡或动画 如果是 则一些 CSS 过度
class
会在适当时机被添加和移除 - 如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。
基于 CSS 的过度效果
CSS 过渡 class
v-enter-from
:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。v-enter-active
:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。v-enter-to
:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是v-enter-from
被移除的同时),在过渡或动画完成之后移除。v-leave-from
:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。v-leave-active
:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。v-leave-to
:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是v-leave-from
被移除的同时),在过渡或动画完成之后移除。
v-enter-active
和 v-leave-active
给我们提供了为进入和离开动画指定不同速度曲线的能力
为过渡效果命名
1 | <Transition name="fade"> |
性能考量
动画所用的一般都是 transfrom
和 opacity
这些属性来制作动画 这些性能比较好
- 他们在动画过程中不会影响到 DOM 结构 因此不会每一帧都触发昂贵的 CSS 布局重新计算
- 大多数的现代浏览器都可以在
transfrom
动画时利用 GPU 进行硬件加速
CSS-Triggers 可以查询哪些属性会在执行动画时触发 css 布局变动