Vue 3 深入响应式原理 - 聊一聊响应式构建的那些经历

// -------------创建响应式-------------

// set 数组,用作保存所有的运算函数

let deps = new Set();

// 保存运算函数

function track() {

deps.add(effect);

}

// 触发器,执行所有的运算函数

function trigger() {

deps.forEach((effect) => effect());

}

// -------------创建数据源-------------

// 声明商品对象,为数据源

let product = {

price: 10,

quantity: 2

};

// 声明总价格

let total = 0;

// 运算总价格的匿名函数

let effect = () => {

total = product.price * product.quantity;

};

// -------------执行响应式-------------

// 保存运算函数

track();

// 运算 总价格

effect();

console.log(总价格:${total}); // 总价格:20

// 修改数据源

product.quantity = 5;

// 数据源被修改,执行触发器,重新运算所有的 total

trigger();

console.log(总价格:${total}); // 总价格:50

你对你的 创造 非常骄傲,并且开始把它推荐给周边的朋友进行使用。但是很快,就有人提出了问题:我 希望把响应式作用到对象的具体属性中 ,而不是 一个属性改变,全部计算重新执行

你希望:使每个属性具备单独的响应性


响应性绑定对象,导致 一个属性改变,全部计算重新执行。所以你希望把响应式作用到对象的具体属性中,只 重新运算该属性相关的内容

为了实现这个功能,你需要借助 Map 对象

Mapkey:val 的形式存储数据,你希望以 属性为 key,以该属性相关的运算方法集合为 val。以此你构建了一个 depsMap 对象,用来达到你的目的:

// -------------创建响应式-------------

// Key:Val 结构的集合

let depsMap = new Map();

// 为每个属性单独保存运算函数,从而让每个属性具备自己独立的响应式

function track(key, eff) {

let dep = depsMap.get(key)

if (!dep) {

depsMap.set(key, (dep = new Set()))

}

dep.add(eff)

}

// 触发器,执行指定属性的运算函数

function trigger(key) {

// 获取指定函数的 dep 数组

const dep = depsMap.get(key);

// 遍历 dep,执行指定函数的运算函数

if (dep) {

dep.forEach((eff) => eff());

}

}

// -------------创建数据源-------------

// 声明商品对象,为数据源

let product = {

price: 10,

quantity: 2

};

// 声明总价格

let total = 0;

// 运算总价格的匿名函数

let effect = () => {

total = product.price * product.quantity;

};

// -------------执行响应式-------------

// 保存运算函数

track(‘quantity’, effect);

// 运算 总价格

effect();

console.log(总价格:${total}); // 总价格:20

// 修改数据源

product.quantity = 5;

// quantity 被修改,仅仅触发 quantity 的响应式

trigger(‘quantity’);

console.log(总价格:${total}); // 总价格:50

你的客户总是非常挑剔的,很快他们抛出了新的问题:我的程序不可能只有一个对象!你需要让所有的对象都具备响应式!

你希望:使不同对象的不同属性具备单独的响应性


你的响应式需要覆盖程序中的所有对象,否则你的代码将毫无意义!

为了达到这个目的,你需要将 对象、属性、运算方法 进行分别的缓存,现有的 depsMap 已经没有办法满足你了。你需要更加强大的 Map ,让 每个对象 都有一个 Map 。它就是 WeakMap

WeakMap 对象是一组键/值对的集合。其键必须是对象,而值可以是任意的。

借助 WeakMap 你让每个对象都拥有了一个 depsMap

// -------------创建响应式-------------

// weakMap:key 必须为对象,val 可以为任意值

const targetMap = new WeakMap()

// 为不同对象的每个属性单独保存运算函数,从而让不同对象的每个属性具备自己独立的响应式

function track(target, key, eff) {

// 获取对象所对应的 depsMap

let depsMap = targetMap.get(target)

if (!depsMap) {

targetMap.set(target, (depsMap = new Map()))

}

// 获取 depsMap 对应的属性

let dep = depsMap.get(key)

if (!dep) {

depsMap.set(key, (dep = new Set()))

}

// 保存不同对象,不同属性的 运算函数

dep.add(eff)

}

// 触发器,执行指定对象的指定属性的运算函数

function trigger(target, key) {

// 获取对象所对应的 depsMap

let depsMap = targetMap.get(target)

if (!depsMap) {

return

}

// 获取指定函数的 dep 数组

const dep = depsMap.get(key);

// 遍历 dep,执行指定函数的运算函数

if (dep) {

dep.forEach((eff) => eff());

}

}

// -------------创建数据源-------------

// 声明商品对象,为数据源

let product = {

price: 10,

quantity: 2

};

// 声明总价格

let total = 0;

// 运算总价格的匿名函数

let effect = () => {

total = product.price * product.quantity;

};

// -------------执行响应式-------------

// 保存运算函数

track(product, ‘quantity’, effect);

// 运算 总价格

effect();

console.log(总价格:${total}); // 总价格:20

// 修改数据源

product.quantity = 5;

// quantity 被修改,仅仅触发 quantity 的响应式

trigger(product, ‘quantity’);

console.log(总价格:${total}); // 总价格:50

每次数据改变,我都需要重新执行 trigger , 这样太麻烦了!万一我忘了怎么办? 。客户总是会提出一些 改(wu)进(li) 的要求,没办法,谁让人家是客户呢?

你希望:使不同对象的不同属性具备自动的响应性


每次数据改变,我都需要重新执行 trigger ,你的客户发出了这样的抱怨。

如果想要达到这样的目的,那么你需要了解 “数据的行为” , 即:你需要知道,数据在什么时候被赋值,在什么时候被输出

此时你需要借助两个新的对象:

借助 Proxy + Reflect 你成功实现了对数据的监听:

// -------------创建响应式-------------

// weakMap:key 必须为对象,val 可以为任意值

const targetMap = new WeakMap()

// 为不同对象的每个属性单独保存运算函数,从而让不同对象的每个属性具备自己独立的响应式

function track(target, key, eff) {

// 获取对象所对应的 depsMap

let depsMap = targetMap.get(target)

if (!depsMap) {

targetMap.set(target, (depsMap = new Map()))

}

// 获取 depsMap 对应的属性

let dep = depsMap.get(key)

if (!dep) {

depsMap.set(key, (dep = new Set()))

}

// 保存不同对象,不同属性的 运算函数

dep.add(eff)

}

// 触发器,执行指定对象的指定属性的运算函数

function trigger(target, key) {

// 获取对象所对应的 depsMap

let depsMap = targetMap.get(target)

if (!depsMap) {

return

}

// 获取指定函数的 dep 数组

const dep = depsMap.get(key);

// 遍历 dep,执行指定函数的运算函数

if (dep) {

dep.forEach((eff) => eff());

}

}

// 使用 proxy 代理数据源,以达到监听的目的

function reactive(target) {

const handlers = {

get(target, key, receiver) {

track(target, key, effect)

return Reflect.get(target, key, receiver)

},

set(target, key, value, receiver) {

let oldValue = target[key]

let result = Reflect.set(target, key, value, receiver)

if (result && oldValue != value) {

trigger(target, key)

}

return result

},

}

return new Proxy(target, handlers)

}

// -------------创建数据源-------------

// 声明商品对象,为数据源

let product = reactive({ price: 10, quantity: 2 })

// 声明总价格

let total = 0;

// 运算总价格的匿名函数

let effect = () => {

total = product.price * product.quantity;

};

// -------------执行响应式-------------

effect()

console.log(总价格:${total}); // 总价格:20

// 修改数据源

product.quantity = 5;

console.log(总价格:${total}); // 总价格:50

你心满意足,觉得你的代码无懈可击。突然耳边响起客户 赏(bu)心(he)悦(shi)目(yi) 的声音:你不觉得每次执行 effect 很反人类吗?

你希望:让运算自动执行


自动化!自动化!所有的操作都应该自动运行!

为了可以让运算自动运行,你专门设计了一个 effect 函数,它可以 接收运算函数,并自动执行

// -------------创建响应式-------------

// weakMap:key 必须为对象,val 可以为任意值

const targetMap = new WeakMap()

// 运算函数的对象

let activeEffect = null;

// 为不同对象的每个属性单独保存运算函数,从而让不同对象的每个属性具备自己独立的响应式

function track(target, key) {

if (activeEffect) {

// 获取对象所对应的 depsMap

let depsMap = targetMap.get(target)

if (!depsMap) {

targetMap.set(target, (depsMap = new Map()))

}

// 获取 depsMap 对应的属性

let dep = depsMap.get(key)

if (!dep) {

depsMap.set(key, (dep = new Set()))

}

// 保存不同对象,不同属性的 运算函数

dep.add(activeEffect)

}

}

// 触发器,执行指定对象的指定属性的运算函数

function trigger(target, key) {

// 获取对象所对应的 depsMap

let depsMap = targetMap.get(target)

if (!depsMap) {

return

}

// 获取指定函数的 dep 数组

const dep = depsMap.get(key);

// 遍历 dep,执行指定函数的运算函数

if (dep) {

dep.forEach((eff) => eff());

}

}

// 使用 proxy 代理数据源,以达到监听的目的

function reactive(target) {

const handlers = {

get(target, key, receiver) {

track(target, key)

return Reflect.get(target, key, receiver)

},

set(target, key, value, receiver) {

let oldValue = target[key]

let result = Reflect.set(target, key, value, receiver)

if (result && oldValue != value) {

trigger(target, key)

}

return result

},

}

return new Proxy(target, handlers)

}

// 接收运算函数,执行运算函数

function effect(eff) {

activeEffect = eff;

activeEffect();

activeEffect = null;

}

// -------------创建数据源-------------

// 声明商品对象,为数据源

let product = reactive({ price: 10, quantity: 2 })

// 声明总价格

let total = 0;

// 通过 effect 运算总价格

effect(() => {

total = product.price * product.quantity;

})

// -------------执行响应式-------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值