【手撕源码】vue3响应式原理解析

举报
猫先生c 发表于 2023/09/28 09:11:28 2023/09/28
【摘要】 一、认识ProxyProxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。new Proxy(target,handler)表示生成一个Proxy实例...

一、认识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();
	}
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。