都知道Vue升级Vue3 好,到底好在哪里呢?【性能更好、体积更小、更好ts支持、更好逻辑抽离】
说Vue3性能比Vue2好、体积更小(打包大小减少 41%,初次渲染快 55%,更新快 133%,内存使用减少 54%)到底性能怎么好呢?
下面将揭开上面两个问题神秘面纱。
一、响应式原理*
响应式是Vue3的一大重要升级, 也是Vue3性能好的一大方面,对比学习一下就会发现Vue3的响应式原理为什么好。
1、defineProperty模拟实现响应式
// 初始化数据
const data = {
name: '张三',
info: {
age: '30',
address: '深圳'
},
years: [20101, 2015, 2021]
}
let arrProto = null // 处理数组时候用的
// 监听数据
observer(data)
// 监听对象属性
function observer(target) {
if (typeof target !== 'object' || target === null) { // 不是对象或数组
return target
}
// 如果是数组, 因为defineProperty不能坚听数组的变化,这也是其一大缺点
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定义各个属性(for in 也可以遍历数组)
for (let key in traget) {
defineReactive(target, key, target[key])
}
}
function defineReactive(target, key, value) {
// 深度监听 , 最开始一上来不管用没用到数据 全部变成响应式
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// 设置新值
value = newValue
// 深度监听 如果新赋的值 又得变成响应式
observer(newValue)
// 触发更行
updateView()
}
}
})
}
// 触发更新
function updateView () {
console.log('视图更新')
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
updateView() //手动触发视图更新 在执行数组原来的方法
oldArrayProperty[methodName].call(this, ...arguments)
// Array.prototype.push.call(this, ...arguments)
}
})
1、proxy模拟实现响应式
// 初始化数据
const data = {
name: '张三',
info: {
age: '30',
address: '深圳'
},
years: [20101, 2015, 2021]
}
const proxyData = observe(data) // 返回响应式数据
// 创建响应式
function observe (target = {}) {
if (typeof target !== 'object' || target == null) {
// 不是对象或数组,则返回
return target
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 监听
}
const result = Reflect.get(target, key, receiver)
// 深度监听
// 性能如何提升的?用的时候才把其变成响应式
return observe(result)
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true
}
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key', key)
} else {
console.log('新增的 key', key)
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否删除成功
}
}
// 生成代理数据
const observeData = new Proxy(target,proxyConf)
return observeData
}
总结: 1、defineProperty 深度监听, 需要一次性递归到底, 性能不好
2、不能监听原生数组变化, 需要重新定义数组的原型
3、不能监听对象新增或删除属性---所以才有Vue.set 和 Vue.delete
但Vue3完美解决上面的问题【Proxy是在get的时候 才会变成响应式】
二、生命周期【Options API 和 Composition API】*
三、理解ref、toRef、toRefs*
1、ref: 生成值类型的响应式数据、可用于模板和reactive、通过.value修改值
export default {
setup () {
const ageRef = ref(20) // 值类型、响应式
const nameRef = ref('terry')
// 建议命名规范都通过xxRef结尾 --- 习惯问题
const state = reactive({
name: nameRef
})
// 通过.value修改值
setTimeout(() => {
ageRef .value= 30
nameRef .value = 'Terry'
})
// 用于template
const elemRef = ref(null)
onMounted(() => {
console.log('ref template', elemRef .value.innerHTML, elemRef.value)
})
retrun {
ageRef,
nameRef,
elemRef
}
}
}
2、toRef: 针对一个响应式对象(reactive)的prop、创建一个ref,具有响应式、两者保持引用关系。
export default {
setup () {
const state = reactive({ // 针对响应式对象的, 如果用于普通对象, 产出结果不具备响应式
age: 20,
name: 'terry'
})
const nameRef = toRef(state, name) // 响应式对象(reactive)的prop
// 两者保存引用
setTimeout(() => {
state.name= 'terry a'
})
setTimeout(() => {
nameRef.value= 'terry b'
})
// 上面两个都是双向改变的
return {
state,
nameRef
}
}
}
3、toRefs:将响应式对象(reactive封装)转换为普通对象、对象的每个prop都是对应的ref、两者保持引用关系。
export default {
setup () {
const state = reactive({
age: 20,
name: 'terry'
})
const stateAsRefs = toRefs(state) // 将响应式对象(reactive封装)转换为普通对象 但是每个prop都是对应的ref, 两者保持引用关系
setTimeout(() => {
state .name= 'terry a'
})
return stateAsRefs
// 如果返回下面的
return {
// state, // 在模版里就得state.age
// ...state, // 解构出来 则失去响应式了
}
}
}
最佳使用方式
1、用reactive做对象的响应式, 用ref做值类型的响应式
2、setup中返回toRefs(state),或者toRef(state, 'xxx')
3、ref的变量命名建议都用xxRef对象时, 使用toRef
4、合成函数返回响应式
深入理解上述api
1、为什么需要ref?
返回值类型, 会丢失响应式
如在setup、computed、合成函数,都有可能返回值类型
Vue如不定义, 用户将自造ref, 反而混乱
2、为什么需要.value
ref是一个对象(不丢失响应式), value存储值
通过.value属性的get 和 set实现响应式
用于模版、reactive时,不需要.value,其他情况都需要
3、为何需要toRef 、toRefs
初衷: 不丢失响应式的情况下, 把对象数据分解/扩散
前提:针对的是响应式对象(reactive封装的)非普通对象
注意:不创造响应式, 而是延续响应式
四、emits 【组件事件现在需要在 emits选项中声明】
// 父组件
<template>
<HelloWorld @onSayHello />
</template>
// 子组件
export default {
emits: ['onSayHello'],
setup(props, {emit}) {
emit('onSayHello', 'vue3') // 其父组件触发的事件
}
}
五、多事件处理器
<template>
<!-- 这两个 one() 和 two() 将执行按钮点击事件 -->
<button @click="one($event), two($event)">
Submit
</button>
</template>
export default {
methods: {
one(event) {
// 第一个事件处理器逻辑...
},
two(event) {
// 第二个事件处理器逻辑...
}
}
}
六、fragment
七、移除.sync
// 2.x
<ChildComponent :title.sync="pageTitle" />
// 3.x
<ChildComponent v-model:title="pageTitle" />
八、异步组件
// vue2.x
components: {
'my-component': () => import('./my-cpmponent.vue')
}
// vue 3.x
components: {
'my-component': defineAsyncComponent(() => {
import('./my-cpmponent.vue')
})
}
九、移除filter
十、Teleport 【对应react的portal】
<!-- data中设置modalOpen:false -->
<button @click="modalOpne = true">
open full screen modal!!
</button>
<teleport to="body">
<div v-if="modalOpen">
<div>
telePort弹窗 (父元素body)
</div>
</div>
</teleport>
十一、Suspense
十二、Compositon API --- reactvie响应式对象
十三、Compositon API --- ref相关的见上面
十四、Compositon API --- readonly
十五、Compositon API --- watch 与 watchEffect
1、两者都可监听data属性变化
2、watch需要明确监听哪个属性
3、watchEffect会根据其中的属性, 自动监听其变化
*watchEffect默认初始化执行一次 【要收集要监听的数据】
十六、Compositon API --- setup
在setup和其他composition API中没有this
可以通过 getCurrentInstance()获取当前实例
如果使用options API 可照常使用this
const instance = getCurrentInstance()
十七、Compositon API --- 生命周期钩子函数
十八、Compositon API --- 实现逻辑复用
1、抽离逻辑代码到一个函数
2、函数命名约定为useXxxx 【react hook 也是】
3、在setup中引用useXxxx函数