对于Vue3

话说,Vue3曾经进行到rc4版本了,4月份beta公布的时候前端圈红红火火,不晓得大家开始学了没

整顿了一些资源,当初开始学习应该还不算晚[狗头]

  • vue-next仓库
  • 20200723 Vue3 官网公布的beta文档
  • Vue3 Roadmap & FAQ
  • Vue3仓库曾经合并的780多个PR
  • 尤大在Vue Mastery的Vue3课:Vue 3 Deep Dive with Evan You
  • 202007 尤大在前端会客厅节目对于Vue3的访谈
  • 202005 The process: Making Vue 3
  • 202004 尤大 - 聊聊 Vue.js 3.0 Beta 官网直播
  • 2018 VueConf 杭州 尤大对于Vue3的演讲视频

vue2 响应式原理回顾

  • 对象响应化:遍历每个key,通过 Object.defineProperty API定义getter,setter
// 伪代码function observe(){    if(typeof obj !='object' || obj == null){        return    }    if(Array.isArray(obj)){        Object.setPrototypeOf(obj,arrayProto)    }else{    const keys = Object.keys()    for(let i=0;i<keys.length;i++){      const key = keys[i]      defineReactive(obj,key,obj[key])    }    }}function defineReactive(target, key, val){  observe(val)  Object.defineProperty(obj, key, {    get(){      // 依赖收集      dep.depend()      return val    },    set(newVal){      if(newVal !== val){        observe(newVal)        val = newVal        // 告诉更新        dep.notify()      }    }  })}
  • 数组响应化:笼罩数组的原型办法,减少告诉变更的逻辑
// 伪代码const originalProto = Array.prototypeconst arrayProto = Object.create(originalProto)['push','pop','shift','unshift','splice','reverse','sort'].forEach(key=>{    arrayProto[key] = function(){        originalProto[key].apply(this.arguments)        notifyUpdate()    }})

vue2响应式痛点

  • 递归,耗费大
  • 新增/删除属性,须要额定实现独自的API
  • 数组,须要额定实现
  • Map Set Class等数据类型,无奈响应式
  • 批改语法有限度

vue3响应式计划

应用ES6的 Proxy 进行数据响应化,解决上述Vue2所有痛点

Proxy能够在指标对象上加一层拦挡/代理,外界对指标对象的操作,都会通过这层拦挡

相比 Object.defineProperty ,Proxy反对的对象操作非常全面:get、set、has、deleteProperty、ownKeys、defineProperty......等等

// reactive 伪代码function reactice(obj){  return new Proxy(obj,{    get(target, key, receiver){      const ret = Reflect.get(target, key, receiver)      return isObject(ret) ? reactice(ret) : ret    },    set(target, key, val, receiver){      const ret = Reflect.set(target, key, val, receiver)      return ret    },    deleteProperty(target, key){      const ret = Reflect.deleteProperty(target, key)      return ret    },  })}

响应式原理

  • 通过 effect 申明依赖响应式数据的函数cb ( 例如视图渲染函数render函数),并执行cb函数,执行过程中,会触发响应式数据 getter
  • 在响应式数据 getter中进行 track依赖收集:建设 数据&cb 的映射关系存储于 targetMap
  • 当变更响应式数据时,触发 trigger 依据 targetMap 找到关联的cb执行
  • 映射关系 targetMap 构造:
targetMap: WeakMap{     target:Map{         key: Set[cb1,cb2...]     }}

手写vue3响应式

大抵构造

// mini-vue3.js/* 建设响应式数据 */function reactice(obj){}/* 申明响应函数cb(依赖响应式数据) */function effect(cb){}/* 依赖收集:建设 数据&cb 映射关系 */function track(target,key){}/* 触发更新:依据映射关系,执行cb */function trigger(target,key){}

reactive

/* 建设响应式数据 */function reactive(obj){  // Proxy:http://es6.ruanyifeng.com/#docs/proxy  // Proxy相当于在对象外层加拦挡  // Proxy递归是惰性的,须要增加递归的逻辑    // Reflect:http://es6.ruanyifeng.com/#docs/reflect  // Reflect:用于执行对象默认操作,更标准、更敌对,能够了解成操作对象的合集  // Proxy和Object的办法Reflect都有对应  if(!isObject(obj)) return obj  const observed = new Proxy(obj,{    get(target, key, receiver){      const ret = Reflect.get(target, key, receiver)      console.log('getter '+ret)      // 跟踪 收集依赖      track(target, key)      return reactive(ret)    },    set(target, key, val, receiver){      const ret = Reflect.set(target, key, val, receiver)      console.log('setter '+key+':'+val + '=>' + ret)      // 触发更新      trigger(target, key)      return ret    },    deleteProperty(target, key){      const ret = Reflect.deleteProperty(target, key)      console.log('delete '+key+':'+ret)      // 触发更新      trigger(target, key)      return ret    },  })  return observed}

effect

/* 申明响应函数cb */const effectStack = []function effect(cb){  // 对函数进行高阶封装  const rxEffect = function(){    // 1.捕捉异样    // 2.fn出栈入栈    // 3.执行fn    try{      effectStack.push(rxEffect)      return cb()    }finally{      effectStack.pop()    }  }  // 最后要执行一次,进行最后的依赖收集  rxEffect()  return rxEffect}

track

/* 依赖收集:建设 数据&cb 映射关系 */const targetMap = new WeakMap()function track(target,key){  // 存入映射关系  const effectFn = effectStack[effectStack.length - 1]  // 拿出栈顶函数  if(effectFn){    let depsMap = targetMap.get(target)    if(!depsMap){      depsMap = new Map()      targetMap.set(target, depsMap)    }    let deps = depsMap.get(key)    if(!deps){      deps = new Set()      depsMap.set(key, deps)    }    deps.add(effectFn)  }}

trigger

/* 触发更新:依据映射关系,执行cb */function trigger(target, key){  const depsMap = targetMap.get(target)  if(depsMap){    const deps = depsMap.get(key)    if(deps){      deps.forEach(effect=>effect())    }  }}

测试demo

<!-- test.html --><div id="app"> {{msg}}</div><script src="./mini-vue3.js"></script><script>  // 定义一个响应式数据  const state = reactive({    msg:'message'  })  // 定义一个应用到响应式数据的 dom更新函数    function updateDom(){        document.getElementById('app').innerText = state.msg    }    // 用effect申明更新函数  effect(updateDom)  // 定时变更响应式数据  setInterval(()=>{    state.msg = 'message' + Math.random()  },1000)</script>

成果:

如果想获取上述代码,放在了这个仓库:mini-vue3-reactive