前言
Vue 3的文章也跟新了不少,相比vue2还是有很多区别的,有许多重要的变化和改进。以下是 Vue 3 相对于 Vue 2 的一些主要区别:
生命周期函数
生命周期函数基本和vue2差不多,名字前加了on
;
具体如下:
onBeforeMount
: 在挂载开始之前被调用。onMounted
: 挂载完成后被调用。onBeforeUpdate
: 数据更新前调用。onUpdated
: 数据更新后调用。onBeforeUnmount
: 在卸载之前被调用,此时组件仍然是可用的。onUnmounted
: 在卸载后被调用,此时组件已经不再可用。
由于引入了 Composition API,组件的逻辑可以通过 setup 函数来定义。setup 函数中的代码会在 beforeCreate 和 created 钩子之前执行。这意味着在 setup 函数中可以执行之前在 beforeCreate 和 created 钩子中执行的逻辑,所以还是能使用beforeCreate
和created
// vue3
<script setup>
import { onMounted } from 'vue'; // 使用前需引入生命周期钩子
onMounted(() => {
// ...
});
onMounted(() => {
// ...
});
</script>
</script>
主要区别在于 Vue 3.x 引入了新的 beforeUnmount 和 unmounted 生命周期钩子,用于取代 Vue 2.x 中的 beforeDestroy 和 destroyed 钩子。
响应式原理
Vue 3 和 Vue 2 在响应式原理上有一些重要的区别,尽管它们的核心思想都是基于数据劫持实现的,但 Vue 3 引入了 Proxy 对象作为数据劫持的主要手段,而 Vue 2 使用的是 Object.defineProperty
,无法监听对象或数组新增、删除的元素。
Vue 3 的响应式原理:
-
Proxy 对象:
- Vue 3 使用了 JavaScript 的 Proxy 对象来实现响应式。Proxy 提供了更灵活且强大的拦截器,可以捕捉更多的操作,包括对象属性的读取、写入、删除等。
-
Reactive 函数:
- 在 Vue 3 中,通过
reactive
函数将一个普通对象转换为响应式对象。这个函数会使用 Proxy 对象来包装原始对象,实现对对象属性的代理。
import { reactive } from 'vue'; const state = reactive({ count: 0 });
- 在 Vue 3 中,通过
-
Ref 函数:
- Vue 3 引入了
ref
函数,用于创建一个包含响应式数据的引用对象。这个函数返回一个具有.value
属性的对象,通过对这个对象进行访问和修改来触发响应式更新。
import { ref } from 'vue'; const count = ref(0);
- Vue 3 引入了
Vue 2 的响应式原理:
-
Object.defineProperty:
- Vue 2 使用
Object.defineProperty
来劫持对象的属性,通过在对象属性的读取和写入时触发 getter 和 setter 函数来实现响应式。
- Vue 2 使用
-
Observer 类:
- 在 Vue 2 中,一个被观察的对象会被转化为 Observer 类的实例,Observer 类会递归地为对象的每个属性创建 getter 和 setter。
-
getter 和 setter:
- 在 Vue 2 中,当访问或修改对象的属性时,通过 getter 和 setter 来触发依赖的收集和更新。
// 伪代码,用于说明 Vue 2 的响应式原理 function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { get() { // 收集依赖 // ... return val; }, set(newVal) { // 更新视图 // ... val = newVal; } }); }
Composition API
Vue 3 中的 Composition API 是一种新的组织组件逻辑的方式,它提供了更灵活和强大的工具来组织和重用组件代码。Composition API 的引入使得代码更易于理解、维护,同时也更好地支持 TypeScript。以下是 Composition API 的一些核心概念和特性:
1. setup 函数:
- Composition API 中的组件逻辑主要集中在
setup
函数中。这个函数在组件实例创建之前调用,它接收props
和context
参数,允许你在组件创建之前进行一些初始化工作。
setup(props, context) {
// 在这里可以进行组件初始化工作
// 可以访问 props 和 context 对象
}
2. Reactive 和 Ref:
- Composition API 提供了
reactive
和ref
函数来创建响应式的数据。reactive
用于创建包含响应式对象,而ref
用于创建包含基本类型值的引用对象。
import { reactive, ref } from 'vue';
setup() {
const state = reactive({ count: 0 });
const count = ref(0);
return { state, count };
}
3. Watch 和 WatchEffect:
- Composition API 提供了
watch
和watchEffect
函数来进行数据的监听。watchEffect
在其依赖的响应式数据变化时会自动运行,而watch
则更加灵活,可以监听特定的数据变化,并执行相应的操作。
import { watch, watchEffect } from 'vue';
setup() {
const state = reactive({ count: 0 });
watchEffect(() => {
console.log(state.count);
});
watch(() => state.count, (newValue, oldValue) => {
console.log(`count 从 ${oldValue} 变为 ${newValue}`);
});
}
4. Lifecycle Hooks:
- Composition API 改变了生命周期钩子的写法,不再使用以前的 Options API 的方式,而是使用
onXxx
的形式。例如,created
钩子变为onCreated
。
setup() {
onMounted(() => {
// 组件挂载后执行的逻辑
});
onUpdated(() => {
// 组件更新后执行的逻辑
});
onUnmounted(() => {
// 组件卸载前执行的逻辑
});
}
5. Provide 和 Inject:
- Composition API 中提供了
provide
和inject
函数,用于父子组件之间的传值。这使得跨组件之间的状态管理更加灵活。
// 父组件
setup() {
const data = reactive({ message: 'Hello from parent' });
provide('data', data);
}
// 子组件
setup() {
const data = inject('data');
console.log(data.message); // 输出 'Hello from parent'
}
6. Refs 和 Reactive:
- Composition API 中的
ref
和reactive
返回的对象在模板中的使用方式变得更加直接,不再需要.value
。
// 在模板中
<template>
<p>{{ count }}</p>
</template>
// 在 setup 函数中
setup() {
const count = ref(0);
return { count };
}
7. Custom Hooks:
- Composition API 允许开发者创建自定义的逻辑复用函数,称为 Custom Hooks。这使得逻辑的重用变得更加方便。
// Custom Hook
export function useCounter() {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment };
}
// 在组件中使用
setup() {
const { count, increment } = useCounter();
return { count, increment };
}
Composition API 引入了这些新的概念和工具,使得组件的逻辑可以更清晰、更可组织,同时也提高了代码的可维护性。它并不是要取代 Options API,而是提供了另一种更灵活的选择。开发者可以根据项目的需要选择使用 Options API 还是 Composition API。
Teleport
Teleport 是 Vue 3 中的一个新特性,它允许你在组件树中的任何位置渲染元素,使得在 DOM 中的位置可以脱离组件的父子关系。这对于创建全局弹窗、模态框或者在组件外渲染一些内容非常有用。Teleport 的核心思想是将要渲染的内容传送(teleport)到另一个位置。
基本用法:
<template>
<div>
<button @click="showModal = true">Show Modal</button>
<teleport to="body">
<Modal v-if="showModal" @close="showModal = false">
<!-- Modal 内容 -->
</Modal>
</teleport>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
data() {
return {
showModal: false
};
}
};
</script>
在上面的例子中,<Modal>
组件的内容被传送到 <teleport to="body">
下,也就是挂载到了 body
元素下。这样 Modal 就可以在 DOM 结构中脱离当前组件的位置,使得其可以在整个页面中显示,而不受到组件嵌套的限制。
Teleport 的属性:
-
to: 指定要传送到的目标位置,可以是一个 CSS 选择器字符串、DOM 元素或者一个函数。
<teleport :to="document.getElementById('target')"> <!-- 要传送的内容 --> </teleport>
-
disabled: 一个布尔值,用于控制是否启用 Teleport。当为
true
时,Teleport 将不会生效,传送的内容将在组件的父级中渲染。<teleport :to="document.getElementById('target')" :disabled="shouldDisable"> <!-- 要传送的内容 --> </teleport>
注意事项:
-
目标元素的存在性: 在使用 Teleport 时,要确保传送目标存在于 DOM 中。如果目标不存在,Vue 会在控制台给出警告。
-
Scoped CSS: Teleport 不会破坏 Vue 的样式封装,传送的内容仍然受到组件的样式作用域限制。
-
事件和过渡: 传送的内容中的事件和过渡仍然可以正常工作,不受 Teleport 影响。
-
性能考虑: Teleport 的使用应当根据具体情况来考虑性能影响。在某些情况下,可能因为跨越组件边界,导致性能损失。因此,在考虑使用 Teleport 时,建议先进行性能测试。
Suspense 和异步组件
Vue3 提供 Suspense 组件,在等待异步组件加载完成前渲染默认的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。
<template>
<Suspense>
<template #default>
<AsyncComponent />
<AsyncData />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
const AsyncComponent = () => import('./AsyncComponent.vue');
const AsyncData = () => fetchData(); // 异步数据获取函数
</script>
Fragments
在vue2中,必须有个根元素包裹组件,不然会运行报错。Vue 3 支持 Fragments,即可以在模板中使用多个根元素而不需要包裹它们在一个父元素中。
Tree-shaking
Tree-shaking 是一种用于剔除未使用代码(dead code)的优化技术,它通过静态分析的方式识别和移除项目中未被引用的模块或代码片段,以减小最终打包体积。Vue 3 被设计为更易于 Tree-shaking,以下是一些关于如何使用 Vue 3 进行 Tree-shaking 的注意事项:
- 按需引入模块:
Vue 3 的模块是按需引入的,这意味着你只需要引入你实际使用的模块,而不是整个库。例如,如果你只使用了 reactive 函数,只需引入该函数:
import { reactive } from 'vue';
这样可以避免引入整个 Vue 3 库,减小了打包体积。
- 使用 Composition API:
Vue 3 的 Composition API 提供了更灵活的组织组件逻辑的方式。由于 Composition API 是按需引入的,你只需要引入你实际使用的功能函数,而不是整个 API:
import { ref, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
onMounted(() => {
console.log('Component is mounted');
});
return { count };
}
};
-
单文件组件的优化:
在单文件组件中,确保只导入你需要的组件和样式,以避免引入整个文件。同时,避免在模板中使用未使用的组件,因为这也可能影响 Tree-shaking 的效果。 -
懒加载和异步组件:
使用懒加载和异步组件可以帮助实现按需加载,仅在需要的时候才加载相关代码。这对于 Tree-shaking 是非常有利的。 -
优化生产构建:
在生产环境中,确保你的构建工具和配置是针对生产环境进行了优化的。例如,使用生产模式构建、开启压缩、移除调试信息等。 -
检查打包结果:
可以通过检查最终打包结果,查看是否成功剔除了未使用的代码。你可以使用工具或者分析打包后的代码,以确保 Tree-shaking 的效果符合预期。
使用这些方法,结合正确的配置和构建工具,可以使 Vue 3 更好地支持 Tree-shaking,帮助你减小项目的最终打包体积。
Diff算法的优化
Vue 3 在 Virtual DOM 的 diff 算法上进行了一系列的优化,主要目标是提高更新性能、减少不必要的操作、以及更好地利用现代浏览器的特性。以下是一些 Vue 3 中 Virtual DOM diff 算法的优化:
- 静态树提升:
Vue 3 引入了静态树提升,通过标记静态节点,可以在渲染时跳过对这些节点的处理,减少了 Virtual DOM 的操作和对比。这使得在一些场景下,特别是对于大型列表或者频繁更新的组件,能够提高性能。
- 源码生成优化:
Vue 3 利用了编译器生成的源码的优势。在运行时,不再需要对模板进行解析,而是直接使用生成的 JavaScript 代码进行渲染。这样避免了一些不必要的运行时开销,提高了渲染性能。
- Cache Handlers:
Vue 3 中使用了缓存处理器(cache handlers)来缓存动态组件和事件处理器。这样在多次渲染中,如果组件和事件处理器没有发生变化,可以直接复用之前的结果,减少了重复计算的开销。
- 事件侦听器的优化:
Vue 3 在处理事件侦听器时引入了更高效的机制。在处理相同事件的不同侦听器时,Vue 3 会尽量合并它们,以减少事件绑定和解绑时的性能开销。
- Patch Flag:
Vue 3 引入了 patch flag 的概念,用于快速判断两个节点是否相同,从而避免不必要的深度对比。Patch flag 会记录节点的特定信息,例如节点的类型、属性、子节点等,以帮助更快速地进行比较。
6. Fragments 和合并的 Slot 虚拟节点:
Vue 3 支持 Fragments,使得组件的模板中可以有多个根节点。这样在渲染时就不需要额外的根节点包裹,减少了 Virtual DOM 的节点数量。对于一些 slot 的合并也进行了优化,减少了 Virtual DOM 的操作。
7. Block Tree Walking:
Vue 3 引入了块级树遍历(Block Tree Walking)的概念,对于动态组件和模板的动态块,Vue 3 能够更高效地处理。
更好的 TypeScript 支持
Vue 3 对 TypeScript 提供了更好的支持,它的代码库和构建工具都是用 TypeScript 编写的,同时也提供了一些特性来优化 TypeScript 开发体验。
自定义模板块的 TypeScript 支持:Vue 3 中对于使用自定义模板块的 TypeScript 支持更加友好,例如对于 defineProps、defineEmits 等。
v-model
在 Vue 3 中,v-model
的使用方式相对于 Vue 2 有一些变化,主要是为了提供更灵活的用法和更好的支持。
1. v-model
的参数化:
在 Vue 3 中,v-model
可以接收参数,可以自定义 prop 和 event 的名称。这使得 v-model
在组件内的用法更加灵活,不再限定于默认的 modelValue
和 update:modelValue
。
<template>
<MyComponent v-model:customProp="value" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const value = ref('');
return { value };
}
};
</script>
在上面的例子中,v-model
使用了 :customProp
,这样在 MyComponent
内部会使用 customProp
作为 prop 名称和 update:customProp
作为事件名称。
2. v-model
的修饰符支持:
Vue 3 引入了修饰符 API,这使得你可以在 v-model
上使用修饰符,例如 .lazy
和 .trim
。
<template>
<input v-model.lazy="value" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const value = ref('');
return { value };
}
};
</script>
在上面的例子中,.lazy
修饰符表示在 input
的 change
事件之后更新数据,而不是在 input
的 input
事件触发时即时更新数据。
3. v-model
在自定义组件上的使用:
在 Vue 3 中,自定义组件上的 v-model
更加灵活。你可以通过 model
选项自定义 prop 和 event 的名称。
<template>
<MyComponent v-model:customProp="value" />
</template>
<script>
import { ref } from 'vue';
export default {
props: {
customProp: String
},
emits: ['update:customProp'],
setup(props) {
const value = ref('');
return {
value,
// 通过手动触发事件来更新父组件的数据
updateValue(newValue) {
props['update:customProp'](newValue);
}
};
}
};
</script>
小结
Vue 3 在性能、可维护性、功能和 TypeScript 支持等方面都有很多的提升,但也需要开发者适应一些新的概念和 API。如果要升级到 Vue 3,建议查看官方迁移指南。