文章目录
- 1 生命周期的变化
- 2 使用proxy代替defineProperty
- 2.1 Object.defineProperty()语法
- 2.2 Proxy的语法
- 3 Diff算法的提升
- 3.1 以往的渲染策略
- 3.2 Vue3的突破
- 4 TypeScript的支持
- 5 优化打包体积
- 6 新的响应性 API
- 6.1 reactive()
- 6.2 `<script setup>`
- 6.3 nextTick()
- 6.4 reactive() 的局限性
- 6.5 ref()
1 生命周期的变化
Vue2.x | Vue3 |
---|---|
beforeCreate | 使用 setup() |
created | 使用 setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
使用setup
代替了之前的beforeCreate
和created
,其他生命周期名字有些变化,功能都是没有变化的
2 使用proxy代替defineProperty
Vue2双向绑定的核心是Object.defineProperty()
,Vue3的核心是Proxy
2.1 Object.defineProperty()语法
Object.defineProperty( Obj, 'name', {
enumerable: true, //可枚举
configurable: true, //可配置
// writable:true, //跟可配置不能同时存在
// value:'name', //可写死直
get: function () {
return def
},
set: function ( val ) {
def = val
}
} )
vue为什么对数组对象的深层监听无法实现,因为组件每次渲染都是将data里的数据通过defineProperty
进行响应式或者双向绑定上,之前没有后加的属性是不会被绑定上,也就不会触发更新渲染
缺点:
Object.defineProperty
不支持数组的拦截- 只能重定义属性的
读取(get)
和设置(set)
行为
2.2 Proxy的语法
Proxy
,可以重定义更多的行为,比如 in
、delete
、函数调用
等更多行为。
Proxy
直接可以劫持整个对象,并返回一个新对象,不管是操作便利程度还是底层功能上都远强于Object.defineProperty
。
//两个参数,对象,13个配置项
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
},
set: function(target, prop, val) { // 拦截写入操作
if (typeof val == 'number') {
target[prop] = val;
return true;
} else {
return false;
}
}
...13个配置项
};
const p = new Proxy({}, handler);
3 Diff算法的提升
3.1 以往的渲染策略
vue2将template模板编译成渲染函数来返回虚拟DOM树。Vue框架通过递归遍历两个虚拟DOM树,并比较每个节点上的每个属性,来确定实际DOM的哪些部分需要更新。
3.2 Vue3的突破
编译器分析模板并生成带有优化提示的代码,而运行时尽可能获取提示并采用快速路径。这里有三个主要的优化:
- 在DOM树级别。将一个模板分成
由没有动态改变节点结构的模板指令(例如v-if和v-for)
分隔的嵌套“块”,则每个块中的节点结构将再次完全静态。当我们更新块中的节点时,我们不再需要递归遍历DOM树 - 该块内的动态绑定可以在一个平面数组中跟踪。 - 编译器积极地检测模板中的静态节点、子树甚至数据对象,并在生成的代码中将它们提升到渲染函数之外。这样可以避免在每次渲染时重新创建这些对象,从而大大提高内存使用率并减少垃圾回收的频率。
- 在元素级别。编译器还根据需要执行的更新类型,为每个具有动态绑定的元素生成一个优化标志。例如,具有动态类绑定和许多静态属性的元素将收到一个标志,提示只需要进行类检查。运行时将获取这些提示并采用专用的快速路径。
4 TypeScript的支持
Vue3
借鉴了react hook
实现了更自由的编程方式,提出了Composition API
,Composition API
不需要通过指定一长串选项来定义组件,而是允许用户像编写函数一样自由地表达、组合和重用有状态的组件逻辑,同时提供出色的TypeScript
支持
5 优化打包体积
在Vue 3中,通过将大多数全局API
和内部帮助程序移动到Javascript
的module.exports
属性上实现这一点。这允许现代模式下的module bundler
能够静态地分析模块依赖关系,并删除与未使用的module.exports
属性相关的代码。模板编译器还生成了对树抖动
友好的代码,只有在模板中实际使用某个特性时,该代码才导入该特性的帮助程序。
6 新的响应性 API
6.1 reactive()
我们可以使用 reactive()
函数创建一个响应式对象或数组
reactive()
返回的是一个原始对象的 Proxy
,它和原始对象是不相等的
import { reactive } from 'vue'
export default {
// `setup` 是一个专门用于组合式 API 的特殊钩子函数
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// 暴露 state increment 函数 到模板
return {
state,
increment
}
}
}
要在组件模板中使用响应式状态,需要在 setup()
函数中定义并返回。
6.2 <script setup>
可以使用 <script setup>
来大幅度地简化代码。
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
<script setup>
中的顶层的导入和变量声明可在同一组件的模板中直接使用。你可以理解为模板中的表达式和 <script setup
> 中的代码处在同一个作用域中。
6.3 nextTick()
DOM 的更新并不是同步的。相反,Vue 将缓冲它们直到更新周期的 “下个时机” 以确保无论你进行了多少次状态更改,每个组件都只更新一次。
若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick()
这个全局 API:
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// 访问更新后的 DOM
})
}
6.4 reactive() 的局限性
- 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
- 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
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)
6.5 ref()
Vue 提供了一个 ref()
方法来允许我们创建可以使用任何值类型的响应式 ref
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value
。
和响应式对象的属性类似,ref
的 .value
属性也是响应式的。同时,当值为对象类型时,会用 reactive()
自动转换它的 .value
。
ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:
const obj = {
foo: ref(1),
bar: ref(2)
}
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)
// 仍然是响应式的
const { foo, bar } = obj
当 ref
在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value
。下面是计数器例子,用 ref()
代替:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- 无需 .value -->
</button>
</template>