分享Vue3
Vue3.0新特性
响应式原理vue2的defineProperty变为Proxy
概述:
对象:会递归得去循环vue得每一个属性,(这也是浪费性能的地方)会给每个属性增加getter和setter,当属性发生变化的时候会更新视图。
数组:重写了数组的方法,当调用数组方法时会触发更新,也会对数组中的每一项进行监控。
Object.defineProperty的缺陷
- 对象只监控自带的属性,新增的属性不监控,也就不生效。若是后续需要这个自带属性,就要再初始化的时候给它一个undefined值(data里面初始化),后续再改这个值
- 数组的索引发生变化或者数组的长度发生变化不会触发实体更新。可以监控引用数组中引用类型值,若是一个普通值并不会监控,例如:[1, 2, {a: 3}] ,只能监控a
Object.defineProperty
- Vue 将遍历 data 里的所有 property ,使用 Object.defineProperty 把这些 property
全部转为 getter/setter。 - getter/setter在内部让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
- 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 “属性” 记录为依赖。之后当依赖项的setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
- 由于 JavaScript 的限制,Vue 不能检测数组的变化。
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的
- 为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue相同的效果,同时也将在响应式系统内触发状态更新:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
//使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名
vm.$set(vm.items, indexOfItem, newValue)
- 为了解决第二类问题,你可以使用 splice:
vm.items.splice(newLength)
- vue使用装饰者模式,对数组的方法进行扩展;
// 从数组原型中获取这7个方法,并覆盖为可以发送更新通知的函数实现
// 提取vue的原型链
var arrProto = Array.prototype; //因为要调用老方法
// 拷贝
var arrayMethods = Object.create(arrProto);
//会改变原数组的方法7个
var arr = ['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'];
arr.forEach((method)=>{
arrayMethods[method]=function(){
var original=arrProto[method]; //拿出老方法
var result = original.apply(this,args); //调用老方法
dep.notify(); //新方法:调用视图更新
return result;
}
})
Proxy
Proxy消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的这些限制:无法监听 属性的添加和删除、数组索引和长度的变更,并可以支持 Map、Set、WeakMap 和 WeakSet!
详细看这篇文章
初探 Vue3.0 中的一大亮点——Proxy !
new Vue()与createApp()
Vue3 之前使用 new Vue() 来构建应用根组件,现在改为 createApp() ,调用 $mount 方法进行挂载
let app = new Vue() =>
let app = createApp(App);
app.mount("#app")
Vue3 之前使用 Vue.use() 注册api,现在是挂到具体实例对象上
Vue.use => 实例对象.use()
composition API 与 options api的区别
options
- 包含了data,methods,watch,props等描述组件的选项
- Vue 是通过 Options (选项) 的方式对外提供接口,通过已经指定好的不同的 Option 来完成指定的功能
- 调用时机:创建组件实例,然后初始化 props ,紧接着就调用setup 函数。从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用
- 原来视图中可使用的数据来源很多: data,props,computed,methods,inject
这种做法数据来源太多,容易造成混乱
使用mixin复用代码会导致命名冲突
composition API
- 基于函数的api
setup函数
- setup()函数是Vue3.0中,专门为组件提供的新属性。它为基于Composition API的新特性提供了统一的入口。
- 在Vue3中,定义methods、watch、computed、data数据都放在了setup()函数中
- 执行时机:setup()函数会在created()生命周期之前执行。
参数:两个参数,第一个为props(组件接收的props数据可以在setup()函数内访问到)、第二个是context,它是一个上下文对象,可以通过context 来访问Vue的实例 this
响应式数据
- ref
- reactive
- computed
- watchEffect
- watch
响应式api工具
- toRef 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性
- 第一个参数是哪个对象,第二个参数是对象的哪个属性
setup(){
let obj = {name : 'alice', age : 12};
let newObj= toRef(obj, 'name');
function change(){
newObj.value = 'Tom';
console.log(obj,newObj)
}
return {newObj,change}
toRefs
-
当想要从一个组合逻辑函数中返回响应式对象时,用 toRefs 是很有效的,该 API 让消费组件可以 解构 / 扩展返回的对象,并不会丢失响应性
-
可以使用该对象将props属性解构出来并把每一项变为响应式的
-
以此来控制percentage属性响应式的传递给底层组件
-
我们知道ref可以用于创建一个响应式数据,而toRef也可以创建一个响应式数据,那他们之间有什么区别呢?
事实上,如果利用ref函数将某个对象中的属性变成响应式数据,修改响应式数据是不会影响到原始数据toRef与ref的区别
setup(props, ctx) {
const {percentage} = toRefs(props);
}
ref本质是拷贝,修改响应式数据不会影响原始数据;toRef的本质是引用关系,修改响应式数据会影响原始数据
ref数据发生改变,界面会自动更新;toRef当数据发生改变是,界面不会自动更新
toRef传参与ref不同;toRef接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
setup() {
const user = reactive({ age: 1 });
const age = toRef(user, "age");
age.value++;
console.log(user.age); // 2
user.age++;
console.log(age.value); // 3
}
watch与watchEffect的区别
watch
监听ref
let a = ref(0)
let b = ref(1)
watch(() => {
console.log('watch a+b', a.value + b.value)
})
watch(a, () => {
console.log('watch a', a.value + b.value)
})
setTimeout(
() => {
a.value++
}, 1000
)
setTimeout(
() => {
b.value++
}, 2000
)
监听reactive
let a = reactive({count: 1})
let b = reactive({count: 2})
watch(() => {
console.log('a+b', a.count + b.count)
})
watch(() => a.count, () => {
console.log('a.count', a.count + b.count)
})
watch(() => a, () => {
console.log('a', a.count + b.count)
})
setTimeout(() => {
a.count++
}, 1000)
setTimeout(() => {
b.count++
}, 2000)
回调函数停止监听
let a = ref(0)
let stop = watch(() => {
console.log('a', a.value)
})
setTimeout(() => {
a.value++
console.log('change')
stop()
console.log('stop')
}, 1000)
watchEffect
// watchEffect
setup () {
const age = ref(0)
watchEffect(() => console.log(age))
setTimeout(() => {
age.value = 1
}, 1000)
return {
age
}
}
总结watch与watchEffec
watch特性:
1.具有一定的惰性,第一次页面展示的时候不会执行,只有数据变化的时候才会执行
2.参数可以拿到当前值和原始值
3.可以侦听多个数据的变化,用一个侦听起承载
4.watch也可以变为非惰性的 立即执行的 添加第三个参数 immediate: true
watchEffect特性:
1.没有过多的参数 只有一个回调函数
2.自动检测内部代码,代码中有依赖 便会执行
3.不需要传递要侦听的内容 会自动感知代码依赖,不需要传递很多参数,只要传递一个回调函数
4.不能获取之前数据的值 只能获取当前值
6.立即执行,没有惰性,页面的首次加载就会执行。
组件
Fragment
- vue3不会再像vue2一样需要手动添加一个根结点
- vue3会自动使用fragment包裹组件结点
teleport
teleport组件能帮我们将组件渲染在页面指定的仍和地方
- 比如modal组件,vue2中可以通过指令来实现modal组件挂载到body上去,但是比较麻烦
- vue3可以直接使用teleport组件实现这一需求
<teleport to="body" class="modal" v-if="">
...
</teleport>
<teleport to="#modals" class="modal" v-if="">
...
</teleport>
源码层面
重写了虚拟DOM
传统vdom的性能瓶颈:
- 虽然 Vue 能够保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 vdom 树。
- 传统 vdom的性能跟模版大小正相关,跟动态节点的数量无关。在一些组件整个模版内只有少量动态节点的情况下,这些遍历都是性能的浪费。
- JSX 和手写的 render function 是完全动态的,过度的灵活性导致运行时可以用于优化的信息不足
diff算法
- vue3标记和提升了所有静态根节点,diff时只比较动态节点
静态提升
- patch flag,patch算法的时候跳过静态节点,缓存事件处理函数
静态树提升(Static Tree Hoisting)
- 使用静态树提升,这意味着 Vue 3 的编译器将能够检测到什么是静态的,然后将其提升,从而降低了渲 染成本。
- 跳过修补整棵树,从而降低渲染成本,即使多次出现也能正常工作
静态属性提升
使用静态属性提升,Vue3打补丁时将跳过这些属性不会改变的节点
改进的TypeScript支持,编辑器能提供强有力的类型检查和错误及警告