Vue3.0新特性

572 阅读7分钟

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 !

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.立即执行,没有惰性,页面的首次加载就会执行。

源码层面

重写了虚拟DOM

传统vdom的性能瓶颈:

虽然 Vue 能够保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 vdom 树。 传统 vdom的性能跟模版大小正相关,跟动态节点的数量无关。在一些组件整个模版内只有少量动态节点的情况下,这些遍历都是性能的浪费。 JSX 和手写的 render function 是完全动态的,过度的灵活性导致运行时可以用于优化的信息不足

diff算法:

vue3标记和提升了所有静态根节点,diff时只比较动态节点

静态提升

patch flag,patch算法的时候跳过静态节点,缓存事件处理函数

静态树提升(Static Tree Hoisting)

使用静态树提升,这意味着 Vue 3 的编译器将能够检测到什么是静态的,然后将其提升,从而降低了渲 染成本。 跳过修补整棵树,从而降低渲染成本,即使多次出现也能正常工作

静态属性提升

使用静态属性提升,Vue3打补丁时将跳过这些属性不会改变的节点

改进的TypeScript支持,编辑器能提供强有力的类型检查和错误及警告