原稿:语雀 · Vue3.0 新特性 · 原目录:Vue › Vue 知识点 › Vue3.0 新特性
参考链接:
vue3.0六大亮点
|
性能
Vue3.0 响应式改进
vue3.0 使用 proxy 实现响应式,代替了原来的 Object.defineProperty
- 将对象作为数据传递给组件实例时,Vue 会将其转换为 proxy。这个 proxy 使 Vue 能够在 property 被访问或修改时执行依赖项跟踪和更改通知。每个 property 都被视为一个依赖项。
- 首次渲染后,组件将跟踪一组依赖列表——即在渲染过程中被访问的 property。反过来,组件就成为了其每个 property 的订阅者。当 proxy 拦截到 set 操作时,该 property 将通知其所有订阅的组件重新渲染。
const dinner = {
meal: 'tacos'
}
const handler = {
get(target, prop, receiver) {
// track()用于在值变化时跟踪变化(依赖跟踪)
track(target, prop)
return Reflect.get(...arguments)
},
set(target, key, value, receiver) {
// trigger()用于在值变化时设置新值(更新通知)
trigger(target, key)
return Reflect.set(...arguments)
}
}
const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)
这样修改有以下原因 2. Object.defineProperty 只能劫持对象的属性,而 Proxy 是直接代理对象:由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性。而 Proxy 可以直接代理对象;proxy 不需要关心具体的 key,它去拦截的是 「修改 data 上的任意 key」 和 「读取 data 上的任意 key」。所以,不管是已有的 key 还是新增的 key,它都能提供拦截
new Proxy(data, {
get(key) { },
set(key, value) { },
})
- Object.defineProperty 对新增属性需要手动进行 Observe:在 2.x 版本中,给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。这是由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持
- Object.defineProperty 无法监听数组变化
- 新标准:Proxy 作为 ES6 提供的新内置类,原生支持13种拦截操作。作为新标准,JS引擎会继续优化 Proxy ,但 getter 和 setter 基本不会再有针对性优化
vdom 重构
2.0 Vnode 性能瓶颈
|
3.0 优化工作 vue3.0 做出优化,将 vdom 的操作颗粒度变小,每次触发更新不再以组件为单位进行遍历
|
以下代码可在 Vue 3 Template Explorer 尝试

在之前的 vdom 中,如果 msg 值发生改变,整个模版中的所有元素都需要重新渲染。但在 Vue3.0 中,在这个模版编译时,编译器会在动态标签末尾加上 /* Text*/ PatchFlag。只有带 PatchFlag 的 Node 才被认为是动态的元素,会被追踪属性的修改。并且 PatchFlag 会标识动态的属性类型有哪些,比如这里的 TEXT 表示只有节点中的文字是动态的。每一个 Block 中的节点,就算很深,也是直接跟 Block 一层绑定的,可以直接跳转到动态节点而不需要逐个逐层遍历。
- hoistStatic

当使用 hoistStatic 时,所有静态的节点都被提升到 render 方法之外。这意味着,他们只会在应用启动的时候被创建一次,而后随着每次的渲染被不停的复用
Tree-shaking
因为ES6模块是静态引用的,所以可以在编译时判断到底加载了哪些代码。对代码全局做一个分析,找到那些没用被用到的模块、函数、变量,并把这些去掉
在 Vue2.x 版本中,无论从 Vue core 中使用了什么特性,都会在最后的生产代码中出现,因为 Vue 实例是作为一个单一对象导出的,而 bundlers 无法检测到这个对象的哪些属性在代码中使用了
// Vue 2.x - whole `Vue` object is bundled for production
import Vue from 'vue'
Vue.nextTick(() => {})
const obj = Vue.observable({})
Vue3.0 的源码将是 tree-shakeable 的,如果不使用一些功能(如 <keep-alive> 组件或 v-show 指令),它们可以不被包含在生产包中
// Vue 3.x - only imported properties are bundled
import { nextTick, observable } from 'vue'
nextTick(() => {})
const obj = observable({})
Composition API
参考链接:
Options API | vue2.x 中,所有的数据都在 data() 中定义返回,方法定义在 methods 下面,并通过 this 调用——为了向 Vue 组件添加逻辑,需要填充 options,例如 data、methods、computed、watch 等。这种模式下的最大缺点是,这些填充的 options 本身并不是有效的 JavaScript 代码,需要确切了解模板中可以访问哪些属性,以及此关键字的行为。在后台,Vue编译器需要将此属性转换为工作代码 |
Composition API | vue3.x 中,所有的代码逻辑将在 setup() 中实现,包括 data、watch、computed、methods、hooks,并且不再有 this;setup() 在组件生命周期内只执行一次,不会重复执行 |
下面为使用组合 API 的例子:
<template>
<button @click="increment">
Count is: {{ count }}, double is {{ double }}, click to increment.
</button>
</template>
<script>
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
watch(count, (newValue, oldValue) => {
console.log('The new count value is: ' + count.value)
})
function increment() {
count.value++
}
onMounted(() => console.log('component mounted!'))
return {
count,
double,
increment
}
}
}
</script>
- setup() 只是一个函数,向模板返回属性和函数。在其中声明所有的响应式属性、计算属性、watchers 和生命周期钩子,然后返回,这样就可以在模板中使用。 没有从 setup() 中返回的东西将不能在模板中使用
- 用 ref 创建响应式引用,ref 接受参数并返回它包装在具有 value property 的对象中,然后可以使用该 property 访问或更改响应式变量的值
- 用 computed 创建 computed 属性
- 用 watch 侦听响应式更改
- 用 onMounted 创建 mount 生命周期钩子
Composition API:关注点分离 用组件的选项 (data、computed、methods、watch) 组织逻辑在大多数情况下都有效。然而,当组件变得更大时,逻辑关注点的列表也会增长,这可能会导致组件难以阅读和理解。这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,必须不断地“跳转”相关代码的选项块。 |
以文档的 UserRepositories.vue 为例(见官方文档:组合式 API),其实现了 2. 从外部 API 获取该用户名的仓库,并在用户更改时刷新它 4. 使用 searchQuery 字符串搜索存储库 6. 使用 filters 对象筛选仓库
但在 Options API 下,选项虽然分离,但实际上是不同关注点的代码散落在同一组件的不同选项中;使用 Composition API 时:
- 目标一独立为 useUserRepositories.js 模块
- 目标二独立为 useRepositoryNameSearch.js 模块
- 目标三独立为 useRepositoryFilters.js 模块
之后在 UserRepositories.vue 中导入
// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'
通过 Composition API 可以将不同逻辑的实现代码提取到一个独立的组合式函数