一文读懂vue3新特性
vue3.0 的新变化
一、Proxy(核心原理)
-
动机
- 由于 ES5 Object.defineProperty 的限制,Vue 不能检测数组和对象的一些特殊变化。
// vue 2.x
// 对于Object类型
const vm = new Vue({
data:{
a:1
}
})
// vm.a 是响应式的
vm.b = 2
// vm.b 新增属性是非响应式的
// 对于Array类型
const vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的 (通过索引改变一个普通值)
vm.items.length = 2 // 不是响应性的 (修改length)
vue3.x 不存在这些限制。proxy 是针对整个对象层面的代理拦截,而非 defineProperty 针对属性层面做的劫持。
Proxy 实现响应式 Demo
function reactive(target){
if(!isObject(target)){
return target
}
const handlers = {
//属性读取触发get()方法
get(target,key,receiver){
const res = Reflect.get(target,key,receiver)
return res
},
//属性设置触发set()方法
set(target,key,value,receiver){
trigger(target,key)
const res = Reflect.set(target,key,value,receiver)
return res
},
//数据删除触发deleteProperty()方法
deleteProperty(target,key){
const res = Reflect.deleteProperty(target,key)
return res
},
}
const observerd = new Proxy(target,handlers)
return observerd
}
//对象
let obj = {
name:'zyd',
age:26
}
let obj_= reactive(obj)
// obj_.name = 'zyd1'
// obj_.style = '1'
//数组
let arr = new Array(5).fill().map((item,i)=>i)
let arr_ = reactive(arr)
// arr_.push(5)
arr_[1] = 100
arr_[100] = 100
// arr_.length = 0
- Proxy 比 defineProperty 拥有更好的新标准的性能红利。
-
缺陷
不支持 ie11 兼容性测试
二、 Composition-API (核心 Api)
字面理解:组合 Api(vue 希望通过功能函数的组合去实现逻辑拆分与复用)
-
动机
-
横切点关注问题
Options 与 Class Api,代码组织不够聚合,无法按功能去进行代码组织,导致代码散落在 data、生命周期、watch、computed 里。
-
逻辑拆分与复用
vue2.x 代码复用的主要方式是提取可复用组件;纯的计算方法,可以提取为公共方法;但有些不需要模版的公共逻辑(并且与状态、事件、生命周期等紧密关联),就很难抽取,之前的 mixin、高阶组件等方案也都有他们各自带来的弊端。 vue3.x 全新的 composition-API,可以完美解决这些问题,思路与 react-hook 类似,用负责单一功能函数进行组合。
-
用法
setup
-
setup 是一个新的组件选项,(它充当在组件内部使用 Composition API 的入口点。)
// book.vue export default { props: { title: String }, setup(props,context:{attrs, slots, emit}) { console.log(props.title) } }
-
调用时机
在 beforeCreate 之前,全局只调用一次。
-
使用
<template> <div>{{ count }} {{ object.foo }}</div> </template> <script> import { ref, reactive } from 'vue' export default { setup() { const count = ref(0) const object = reactive({ foo: 'bar' }) // 必须return,才可以在模版上使用 return { count, object } } } </script>
-
setup 函数里 this 并不是期望的组件实例,为 undefined,所以不要在 setup 访问 this,尽管这也毫无意义。
reactive
作用:用于实现对象数据类型的响应式侦测
用法: 传入一个普通对象,返回值是一个经过 vue 代理过的响应式对象
const Counter = { setup(){ //reactive实现响应式,适用于一组值 const state = reactive({ count:0 }) const add =()=>{ state.count ++ } //返回一个代理后的对象,用来实现响应式 console.log(state) return { state, add } }, template:`<h1>{{state.count}}</h1><button @click="add">+</button>`, };
ref
作用:用于实现基础数据类型值(例:String、Number)的响应式侦测
用法:传入一个值,返回一个 vue 内部包装过的{value:xxx}对象,并且改变 value 属性可以触发响应式更新,用于模版渲染时,不用.value 这样去访问,vue 内部会自动拆包。
为什么这样设计? 因为在 JavaScript 中,原始类型(例如 Number 或 String)是通过值传递的,而非引用。
//基础类型 let a = 1 let b = a// a变量与b变量已没有关系,实现不了响应式 //对象类型 let a = {value:1} let b = a// a变量与b变量都在引用同一个对象,响应式就不会中断 //ref就是vue内部帮你实现的将普通值转化为一个包装对象的工具。 let a = ref(1) console.log(a) //{value:1} 值转为对象
const Counter = { setup(){ //ref实现响应式,适合单个值场景 const count = ref(0) const add =()=>{ count.value ++ } console.log(count) return { count, add } }, template:`<h1>{{count}}</h1><button @click="add">+</button>`, };
ref 常见的.value 问题 问题有多严重?:前端人因为 Vue3 的 Ref-sugar 提案打起来了! .value 到底什么时候需要 (1) 如果自己的代码取值,需要 (2) watch 等的 vue 自身提供的 api 上,不需要(vue 自动帮你做了拆包) (3) 模版取值不需要 影响:造成开发体验上的割裂 避免:并将所有的 ref 统一命名比如:xxxRef 一定程度可以避免,或者使用 ref:语法糖。
toRef、toRefs
作用:reactive 返回的代理对象在组合函数的传递过程中,必须保持对返回对象的引用,以保证其响应式,该对象不能被 ES6 解构或属性拆解。 toRef 方法
const pos = reactive({ x:0, y:0 }) //将响应式对象某一个属性转化为ref const xRef = toRef(pos,'x') const yRef = toRef(pos,'y')
toRefs 方法
const pos = reactive({ x:0, y:0 }) //将整个响应式对象的全部属性转化为ref,装在一个普通对象中 const posRefsObj = useRefs(pos) //等价于 const posRefsObj = { x:toRef(pos,'x') y:toRef(pos,'y') }
computed
作用: 与 vue2.x 一致,根据函数体内依赖的值的变化,计算出一个新值。 用法: 传入一个计算函数,返回一个包装后的{value:xxx}响应式引用对象。
const Counter = { setup(){ const state = reactive({ count:0 }) //计算count是否是偶数,字体切换红绿颜色 let isOdd = computed(()=>state.count%2 === 0) const add =()=>{ state.count ++ } return { state, isOdd, add } }, template:`<h1 :style="{'color':isOdd?'red':'green'}">{{state.count}}</h1><button @click="add">+</button>`, };
watch
作用: 与 vue2.x 一致,主动监测响应式数据,数据改变后,执行用户传入的回调。
const Counter = { setup(){ //reactive实现响应式,适用于一组值 const state = reactive({ count:0 }) //计算count是否是奇数,字体切换红绿颜色 let isOdd = computed(()=>state.count%2 === 0) watch(isOdd,(newValue)=>{ alert(newValue?'偶数':"奇数") }) const add =()=>{ state.count ++ } return { state, isOdd, add } }, template:`<h1 :style="{'color':isOdd?'red':'green'}">{{state.count}}</h1><button @click="add">+</button>`, };
生命周期
选件API compositonAPI beforeCreate
没有必要* created
没有必要* beforeMount
onBeforeMount
mounted
onMounted
beforeUpdate
onBeforeUpdate
updated
onUpdated
beforeUnmount
onBeforeUnmount
unmounted
onUnmounted
-
与 react-hook 对比
- 心智负担不同 两者都会有一定的心智负担 react-hook 的问题是总担心频繁触发更新,useEffect 可以说是控制代码不被频繁执行最后的逃生舱,但是依赖项数组如果设置不正确,会导致副作用执行时机不正确,还可能会导致取到闭包旧值的 bug; useCallBack 经常需要使用,用来避免触发不必要的子组件渲染。 vue-compositonApi的问题刚好相反,是经常担心触发不了更新,比如解构导致的响应式丢失,vue 引入 ref 解决这个问题,但引起了总是忘记写.value 的新问题。
- 对生命周期的看法不一样 react-hook 有意弱化生命周期的概念,转而倡导渲染副作用的概念,由数据变化引起渲染副作用的执行。或者另一个角度说 react 用 useEffect 实现了生命周期的聚合与封装。
//hook
useEffect(() => {
alert('组件挂载')
return ()=>{
//组件卸载,清除副作用
alert('组件卸载')
}
},[]);
//compositonApi
onMounted(()=>{
alert('组件挂载')
})
onUnmounted(()=>{
alert('组件卸载')
})
vue-compositonApi 还是熟悉的 vue2.x 生命周期,没有增加新的理解成本
compositionAPI 实践 Demo(并与 hook 的对比)常见吸顶及上拉加载 demo。
三、 其他改动
- teleport (传送)
- 组件不再限制只能存在一个根元素
- data 选项,都统一改成函数形式(之前根组件是对象,子组件是函数)
- $children 已废弃,只能通过 ref 获取组件及 dom 了。
- 等等。更多的请参照官方文档
作者简介
郑瑜栋:在团队拥有一个奇怪的外号“=”哥。
转载自:https://juejin.cn/post/7135253368033214501