“这是我参与更文挑战的第9天,活动详情查看: 更文挑战”
前文摘要
通过之前的学习,我们已经通过 Proxy + Reflect 搭建出了一个基础的响应式实现。
- Vue3 响应式原理探索Part 1 - 20行代码实现响应式
- Vue3 响应式原理探索Part 2 - 多重结构的响应式
- Vue3 响应式原理探索Part 3 - Proxy + Reflect + activeEffect
本文,我们将学习 ref 的实现。
为什么需要 Ref
假设出现这样的场景:
let param = reactive({ width: 5, height: 2 });
let size = 0;
let newWidth = 0;
effect(() => {
size = param.width * 2 * param.height;
});
effect(() => {
newWidth = param.width * 2;
});
我们很容易想到将 size 的计算公式改一下:
effect(() => {
size = newWidth * param.height;
});
但这样做是无效的,因为 newWidth 不是响应式的,size 的 effect 并不会重新计算。
如果我们能将 newWidth 改成响应式的,并且还没有将它包裹在另外一个响应式对象里面就最好不过来。
如果你熟悉 Vue3 的 Composition API,你可能会考虑到这个时候可以使用 ref 去创建一个响应式的引用。
根据 Vue 的文档,一个响应式的引入传入一个内部值,然后返回一个可变的响应式引用
ref对象。ref对象有一个单个的属性.value指向传入的内部值。
let param = reactive({ width: 5, height: 2 });
let size = 0;
let newWidth = ref(0);
effect(() => {
newWidth.value = param.width * 2;
});
effect(() => {
size = newWidth.value * param.height;
});
上面的代码还不能工作,因为我们还没有实现 ref 。
如何实现 Ref
基于 Reactive 封装
简单了事,我们可以基于 Reactive 实现。
function ref(initialValue) {
return reactive({ value: initialValue })
}
它是可以工作的,完整的代码如下:
const targetMap = new WeakMap();
let activeEffect = null // The active effect running
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
let dep = depsMap.get(key);
if (dep) {
dep.forEach(element => {
element();
});
}
}
function reactive(target) {
const handler = {
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver);
track(target, key);
return result;
},
set(target, key, value, receiver) {
let oldValue = target[key];
let result = Reflect.set(target, key, value, receiver);
if (result && oldValue != value) {
trigger(target, key);
// console.log('trigger', key);
}
return result;
}
}
return new Proxy(target, handler);
}
function effect(eff) {
activeEffect = eff // Set this as the activeEffect
activeEffect() // Run it
activeEffect = null // Unset it
}
function ref(initialValue) {
return reactive({ value: initialValue })
}
let param = reactive({ width: 5, height: 2 });
let size = 0;
let newWidth = ref(0);
effect(() => {
newWidth.value = param.width * 2;
});
effect(() => {
size = newWidth.value * param.height;
});
console.log(`newWidth is ${newWidth.value}, size is ${size}`); // newWidth is 10, size is 20
param.width = 10;
console.log(`newWidth is ${newWidth.value}, size is ${size}`); // newWidth is 20, size is 40
然而它并不是 Vue3 的实现方式,我们看一下另外一种实现。
基于 Object 的 getter 和 setter 实现
首先我们需要熟悉对象存取器(Object Accessors),他们有时也被称呼为 JavaScript 计算属性(不要将他们和 Vue 计算属性混淆)。他们是原生的 JavaScript, 而不是 Vue 的一个功能。
getter 和 setter 基础学习
let user = {
firstName: 'Gregg',
lastName: 'Pollack',
get fullName() {
return `${this.firstName} ${this.lastName}`
},
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
参考文档:
具体实现
在对象存取器里面,我们可以加入 trigger 及 track 函数。
function ref(raw) {
const r = {
get value() {
track(r, 'value')
return raw
},
set value(newVal) {
raw = newVal
trigger(r, 'value')
},
}
return r
}
完整代码如下:
const targetMap = new WeakMap();
let activeEffect = null // The active effect running
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
let dep = depsMap.get(key);
if (dep) {
dep.forEach(element => {
element();
});
}
}
function reactive(target) {
const handler = {
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver);
track(target, key);
return result;
},
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, handler);
}
function effect(eff) {
activeEffect = eff // Set this as the activeEffect
activeEffect() // Run it
activeEffect = null // Unset it
}
function ref(raw) {
const r = {
get value() {
track(r, 'value')
console.log(raw);
return raw
},
set value(newVal) {
raw = newVal
trigger(r, 'value')
},
}
return r
}
let param = reactive({ width: 5, height: 2 });
let size = 0;
let newWidth = ref(0);
effect(() => {
newWidth.value = param.width * 2;
});
effect(() => {
size = newWidth.value * param.height;
});
// console.log(`newWidth is ${newWidth.value}, size is ${size}`); // newWidth is 10, size is 20
param.width = 6;
// console.log(`newWidth is ${newWidth.value}, size is ${size}`); // newWidth is 12, size is 24
可以看到,我们基于 Object 的 getter 和 setter 实现了一个 ref 。
小结
本人首先介绍了 Ref 的应用场景及相应的两种实现。
- 基于 Reactive 封装
- 基于 Object 的 getter 和 setter 实现
希望对你有所帮助。