介绍
接着 << 深入理解 Vue 3 响应性原理 – Set/Map/WeakMap 函数 >> 基础知识已经具备了,接着就是开始实现了
再看一下, 什么是响应式: 如果我们的一个数据改变了,Vue 知道怎么去更新模板以及会更新模板的计算机属性。
依赖关系 目标图和effects 集
目标图(“target map”),它的类型是 WeakMap
它储存了与每个“响应性对象属性”关联的依赖
depsMap 存储了每个属性的依赖,并且 dep 是一个 effects 集(Set)的依赖。
这些effect应该再值发生变化时重新运行。
//目标图存储着每个响应式对象的依赖
const targetMap = new WeakMap()
function track(target,key){
//获取目标的 deps 图,在我们的例子中是 product
let depsMap = targetMap.get(target)
//如果它还不存在,我们将为这个对象创建一个新的deps图
if(!depsMap){
targetMap.set(target,(depsMap = new Map()))
}
//获得这个属性的依赖对象(quantity)
let dep = depsMap.get(key)
//如果它不存在,我们将创建一个新的 Set
if(!dep){
depsMap.set(key,(dep = new Set()))
}
//把 effect 添加到依赖中
dep.add(effect)
}
function trigger(target,key){
//检查此对象是否拥有依赖的属性
const depsMap = targetMap.get(target)
// 没有则直接返回
if(!depsMap){return}
//否则,我们将检查此属性是否具有依赖
let dep = depsMap.get(key)
//dep 存在,遍历dep,运行每一个 effect
if(dep){
dep.forEach(effect => {effect()})
}
}
let product = {price:5,quantity:2}
let total = 0
let effect = () =>{
total = product.price * product.quantity
}
// 跟踪依赖
track(product, 'quantity');
// 首次触发副作用
effect();
console.log(total);
product.quantity = 3
// 触发函数 trigger 遍历我们存储了的每一个 effect
trigger('quantity');
console.log(total);
如何让代码自动实现响应式
换句话说: 我们该如何知道什么时候使用了 GET 或者 SET
Proxy(代理) & Reflect(反射)
-
三种打印出对象的属性
let product = {a: 1,b: 2} // product.a | product[a] | Reflect.get(product, 'a')
-
Proxy(代理)是另一个对象的占位符,默认情况下对该对象进行委托
这段代码会先调用代理,代理再调用产品,然后产品再返回代理,最后这个产品被返回到控制台日志打印出“2”, 简单来说代理就是一个对象委托。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LbH8re0s-1651237558484)(./img/response/proxy代理关系.jpg)] -
在 Proxy 里使用 Reflect,我们会有一个附加参数,称为 receiver(接收器),它将传递到我们的 Reflect调用中。它保证了当我们的对象有继承自其它对象的值或函数时 this 指针能正确的指向使用(的对象)
let product = {price: 5, quantity: 2} //product let proxiedProduct = new Proxy(product,{//proxiedProduct get(target,key,receiver){ return Reflect.get(target,key,receiver) } }) console.log(proxiedProduct.quantity)
-
我们的set(方法)接收 target、key、value、和 receiver,我们将在 set 被调用时打印出我们的 key 和 value。然后我们再调用 Reflect.set,传递的参数是target、key、value 和 receiver。
let product = {price: 5, quantity: 2} //product let proxiedProduct = new Proxy(product,{//proxiedProduct get(target,key,receiver){ return Reflect.get(target,key,receiver) }, set(target,key,value,receiver){ return Reflect.set(target,key,value,receiver) } }) //测试 proxiedProduct.quantity = 4 console.log(proxiedProduct.quantity)
-
创建一个称为 reactive的函数, 用 handler 包装我们的 get 和 set方法 到常量处理程序中,最后我们将创建一个新的 Proxy,传递我们的 target 和我们的 handler
function reactive(target){ const handler = { get(target,key,receiver){ return Reflect.get(target,key,receiver) }, set(target,key,value,receiver){ return Reflect.set(target,key,value,receiver) } } return new Proxy(target,handler) } // 现在,我们声明产品时,我们只需传递一个对象到响应式函数中 let product = reactive({price: 5, quantity:2 }) product.quantity = 4 console.log(product.quantity)
activeEffect & Ref
-
如何只在 effect 里调用追踪函数
// 引入 activeEffect 变量 let activeEffect = null //它是现在正在运行中的 effect function effect(eff){//声明一个名为 effect 的函数,它接受一个匿名函数 activeEffect = eff activeEffect() activeEffect = null //复位 activeEffect } // 这里就很巧妙了, 执行 effect 函数的时候, 会触发product.price 的get方法 // 把当前 activeEffect 注册到 set 函数中 effect(){ total = product.price * product.quantity }
-
和 Vue 3 响应性源代码中 ref 的使用原理
首先先明白js中的计算属性let user = { firstName: 'Gregg', lastName: 'Pollack', //对象访问器是获取或设置值的函数,所以这里我们可以声明 get fullName,它返回一个名字和姓氏的组合字符串 get fullName(){ return `${this.firstName} ${this.lastName}` }, //声明一个名为fullName 的 setter,它接受一个值,并把它分成两个不同的字符串,设置到名字和姓氏中 set fullName(value){ [this.firstName,this.lastName] = value.split(' ') }, } console.log(`Name is ${user.fullName}`) //Name is Gregg Pollack user.fullName = 'Adam Jahr' console.log(`Name is ${user.fullName}`) //Name is Adam Jahr
ref的实现
function ref(raw){ const r = { get value(){ //调用跟踪函数,追踪我们正在创建的对象r,键是"value",然后返回原始值(传入值) track(r,'value') return raw }, //setter 接收一个新值,把新值赋值给原始值(raw) set value(newVal){ raw = newVal //调用触发函数 trigger(r,'value') }, } //返回对象 return r }
toRef
class ObjectRefImpl {
constructor(_object, _key) {
this._object = _object;
this._key = _key;
this.__v_isRef = true;
}
get value() {
return this._object[this._key];
}
set value(newVal) {
this._object[this._key] = newVal;
}
}
完整代码
缺少 ref 的实现
const targetWeak = new WeakMap()
var activeEffect = null
function effect(eff) {
activeEffect = eff
activeEffect() // 很巧妙
activeEffect = null // 复位
}
// 实现 track 跟踪依赖
function track(target, key) {
if (activeEffect) {
var depMap = targetWeak.get(target)
if (!depMap) {
targetWeak.set(target, (depMap = new Map()))
}
var dep = depMap.get(key)
if (!dep) {
// 为了跟踪依赖,我们将 effect 添加到 Set 中。使用 Set 是因为它不允许拥有重复值
depMap.set(key, (dep = new Set()))
}
//
dep.add(activeEffect)
}
}
// 触发
function trigger(target, key) {
var depMap = targetWeak.get(target)
if (!depMap) return
var dep = depMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
// reactive 绑定响应式
function reactive(target) {
const handle = {
get: function (obj, property, receiver) {
console.log('get----reactive');
const result = Reflect.get(obj, property, receiver)
track(obj, property)
return result
},
set: function (obj, key, value, receiver) {
console.log('set----reactive', obj, target)
const oldValue = obj[key]
const result = Reflect.set(obj, key, value, receiver)
if (oldValue !== result) {
// 如果新值不等旧值
trigger(obj, key)
}
return result
}
}
return new Proxy(target, handle)
}
function ref(raw) { // 这里使用的 js 原生的计算属性
const r = {
get value() {
console.log('get----ref');
// 调用跟踪函数,追踪我们正在创建的对象r,键是"value",然后返回原始值(传入值)
track(r, 'value');
return raw;
},
// setter 接收一个新值,把新值赋值给原始值(raw)
set value(newVal) {
console.log('set----ref');
raw = newVal;
// 调用触发函数
trigger(r, 'value');
}
};
// 返回对象
return r;
}
var product = reactive({
price: 3,
nums: 10
})
var total = 0
// 总价格
effect(() => {
total = product.price * product.nums
})
console.log(total, product)
product.price = 6
console.log(total, product)
console.log(targetWeak)