vue3的响应式(【手撕源码】vue3响应式原理解析(文末抽奖))
🐱 个人主页:不叫猫先生
🙋♂️ 作者简介:2022年度博客之星前端领域TOP 2 ,前端领域优质作者 、阿里云专家博主 ,专注于前端各领域技术 ,共同学习共同进步 ,一起加油呀!
💫优质专栏:vue3从入门到精通 、TypeScript从入门到实践
📢 资料领取:前端进阶资料以及文中源码可以找我免费领取
🔥 前端学习交流:博主建立了一个前端交流群 ,汇集了各路大神 ,一起交流学习 ,期待你的加入!(文末有我wx或者私信) 。一 、认识Proxy
Proxy 用于修改某些操作的默认行为 ,等同于在语言层面做出修改 ,所以属于一种“元编程 ”(meta programming),即对编程语言进行编程 。
Proxy 可以理解成 ,在目标对象之前架设一层“拦截 ” ,外界对该对象的访问,都必须先通过这层拦截 ,因此提供了一种机制 ,可以对外界的访问进行过滤和改写 。
new Proxy(target,handler)表示生成一个Proxy实例,target参数表示所要拦截的目标对象 ,它可以是任意类型的对象 ,包括内置的数组 ,函数等 ,handler也是一个对象 ,用来定制拦截行为 ,当发生某些操作时触发该对象 。二 、原理分析
1.reactive
声明副作用变量 ,如果该变量没有值就不进行追踪 。在 Vue2 的时候 ,有一个“全局变量” ,叫做 Dep.target – watcher,vue3中还要有这么一个全局变量 ,就是activeEffect 。
//副作用变量 let activeEffect;targetMap用来存放依赖
let targetMap = new WeakMap();判断传入的数据data是否为对象 ,需要除去data为null的情况 。因为typeof null === ‘object’,null的机器码都是0 ,object机器码为000
function isObject(data) { return data && typeof data === object }声明reactive函数 ,返回proxy实例 。proxy支持get 、set 、deleteProperty 、has 、ownKeys等方法 。
get 通过Reflect.get(target, key, receiver)获取到属性为key的值 track收集依赖 ret为对象则递归为对象创建proxy代理 set Reflect.set(target, key, value, receiver)设置属性为key的值 trigger 执行更新receiver代表当前proxy对象或者继承proxy的对象,保证传递正确的this 给 getter 、setter。
export function reactive(data) { //判断是否为对象 if (!isObject(data)) return // 返回proxy实例 return new Proxy(data, { get(target, key, receiver) { const ret = Reflect.get(target, key, receiver); // 收集依赖 track(target, key) //如果获取的数据还是对象的话就递归 ,继续为此对象创建 Proxy 代理 return isObject(ret) ? reactive(ret) : ret }, //set修改数据 ,需要返回一个布尔值 set(target, key, value,receiver) { // 首先获取旧值 const oldValue = Reflect.get(target, key, receiver) // 判断新值和旧值是否一样来决定是否更新setter let result = true; // 当新值不等于旧值的时候执行更新草错 if (oldValue !== value) { result = Reflect.set(target, key, value, receiver) // 更新操作 trigger(target, key) } //返回布尔类型 return result }, deleteProperty(target, key) { //首先需要判断是否有要删除的key const hasK = hasKey(target, key) const ret = Reflect.deleteProperty(target, key) //存在key且有值则更新 if(hasK&&ret){ //更新 trigger(target, key) } return ret }, has(target, key) { track(target, key) const ret = Reflect.has(target, key) }, ownKeys(target, key) { track(target) return Reflect.ownKeys(targety) }, }) } // 判断对象中key是否存在 const hasKey = (target, key) => Object.prototype.hasOwnProperty.call(target, key)2.track
track里面会收集各种依赖 ,把依赖关系做成各种映射的关系 ,映射关系就叫 targetMap , 内部拿到这个key ,就可以通过映射关系找到对应的value ,就可以影响这个执行函数 ,
function track(target, key) { // 如果当前没有effect就不执行追踪 if (!activeEffect) return //找target有么有被追踪 let depsMap = targetMap.get(target); //判断target是否为空 ,如果target为空则没被追踪,则set设置一个值 if (!depsMap) targetMap.set(target, (depsMap = new Map())); //判断depsMap中有没有key ,没有key就set一个(判断target.key有没有被追踪) let dep = depsMap.get(key) // 如果key没有被追踪 ,那么添加一个 if (!dep) depsMap.set(key, (dep = new Set())) //触发副作用函数 trackEffect(dep) } //副作用函数 function trackEffect(dep) { //相当于 Dep.target && dep.add(Dep.target) //如果key没有添加activeEffect,则添加一个 if (!dep.has(activeEffect)) dep.add(activeEffect); }3.trigger
修改数据时通过 trigger目标对象找到key ,根据映射关系找到cb函数执行更新视图 。
function trigger(target, key) { // 获取依赖数据 ,对依赖数据循环, const depsMap = targetMap.get(target) console.log(depsMap,depsMap) if (!depsMap) return //如果effect存在则执行run方法 ,run方法就是执行的视图更新回调 depsMap.get(key).forEach(effect => effect && effect.run() ); }4.ref
ref中声明了一个RefImpl类 ,初始化时传入参数init 。
get
追踪变量 ,收集依赖 返回初始化变量的值set
修改旧值 ,赋新值 trigger 更新 export function ref(init) { class RefImpl { constructor(init) { // 接收传过来的参数 this.__value = init; } //获取数据 ,直接返回传过来的数据 get value() { track(this, value) return this.__value } //更新数据 set value(newVal) { this.__value = newVal; trigger(this, value) } } return new RefImpl(init); }5.effect
effect第一个参数是函数 ,如果这个函数中有使用 ref/reactive 对象 ,当该对象的值改变的时候effect就会执行。
function effect(fn,option={}){ //effect 数据类型为ReactiveEffect ,一上来就会执行run方法 ,之后可以自定义执行run方法,即设置option的内容 let __effect = new ReactiveEffect(fn); if(!option.lazy){ __effect.run(); } return __effect }6.ReactiveEffect
声明ReactiveEffect类 ,间接定义effect的数据类型 。
class ReactiveEffect{ constructor(fn){ this.fn = fn; } // 依赖收集之前触发 run(){ activeEffect = this; return this.fn() } }7.computed
计算属性
export function computed(fn){ //只考虑函数情况 let __computed; const e = effect(fn,{lazy:true }) __computed = { get value(){ return e.run(); } } return __computed }8.mount
mount的参数instance相当于整个app ,el相当于挂在的节点
export function mount(instance,el){ // 执行effect effect(function(){ instance.$data && update(instance,el) }) instance.$data = instance.setup(); //更新节点 update(instance,el) function update(instance,el){ //挂载节点的render函数返回值内容复制给节点的innerHTML,进行更新 el.innerHTML = instance.render(); } }三、源码地址
附:源码地址
🌟粉丝福利(抽奖)
《低代码开发实战——基于低代码平台构建企业级应用》
抽奖规则:抽奖助手小程序随机抽奖
活动时间:即日起至2023年4月18日 12:00
温馨提示:参与活动者提前参加博主wx(zbsguilai),以避免错过中奖通知 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!