对于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