首页IT科技vue3的响应式(【手撕源码】vue3响应式原理解析(文末抽奖))

vue3的响应式(【手撕源码】vue3响应式原理解析(文末抽奖))

时间2025-05-06 08:54:40分类IT科技浏览5770
导读:🐱 个人主页:不叫猫先生...

🐱 个人主页:不叫猫先生

🙋‍♂️ 作者简介: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版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
微前端技术(微前端 – qiankun)