本文主要对vue3.x中的toRef,toRefs进行模拟实现,并对其应用场景进行分析
const reactMap = new WeakMap();
const ReactiveFlags = {
IS_REACTIVE : "isReactive"
}
let activeEffect = undefined;
class ReactiveEffect {
active = true;
deps = [];
constructor(fn,scheduler) {
this.fn = fn;
this.scheduler = scheduler;
}
run() {
if(!this.active) {this.fn()};
try {
activeEffect = this;
return this.fn()
} finally {
activeEffect = undefined;
}
}
}
// 处理对象再次代理,可以直接返回
function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE])
}
// 将数据转化成响应式的数据,只能做对象的代理
function reactive(target) {
if(!(typeof target === 'object' && target !== null)) {
return;
}
if(target[ReactiveFlags.IS_REACTIVE]) {
return target
}
let exisProxy = reactMap.get(target);
if(exisProxy) {
return exisProxy
}
const proxy = new Proxy(target,{
get(target,key,receiver) {
if(key === ReactiveFlags.IS_REACTIVE) {
return true
}
track(target,'get',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(oldValue !== value) {
trigger(target,'set',key,oldValue,value)
}
return result
}
});
reactMap.set(target,proxy)
return proxy
}
const targetMap = new WeakMap()
function trigger(target,type,key,oldValue,value) {
const depsMap = targetMap.get(target);
if(!depsMap) return;
const effects = depsMap.get(key);
triggerEffect(effects,'effects')
}
function triggerEffect(effects) {
effects && effects.forEach(effect => {
if(effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
})
}
function track(target,type,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()))
}
trackEffect(dep)
}
function trackEffect(dep) {
if(activeEffect) {
let shouldTrack =!dep.has(activeEffect);
if(shouldTrack) {
dep.add(activeEffect)
activeEffect.deps.push(dep);
}
}
}
function isObject(value) {
return typeof value === 'object' && value !== null;
}
function isFunction(val) {
return typeof val === 'function'
}
function isArray(val) {
return Array.isArray(val)
}
function traversal (value, set = new Set()) {
if(!isObject(value)) return value;
if(set.has(value)) {
return value
}
set.add(value);
for(let key in value) {
traversal(value[key],set);
}
return value
}
class ObjectRefImpl {
constructor(object,key) {
this.object = object;
this.key = key
}
get value() {
return this.object[this.key];
}
set value(newValue) {
this.object[this.key] = newValue;
}
}
function toRef(object,key) {
return new ObjectRefImpl(object,key)
}
function toRefs(object) {
const result = isArray(object)? new Array(object.length): {};
for(let key in object) {
result[key] = toRef(object,key)
}
return result
}
当对响应式数据进行解构时,有可能拷贝的非引用地址而是具体的值的时候,将导致对初始引用的响应性连接丢失。如果是相同的引用地址的话,则响应性连接不会丢失。
测试代码
let r1 = reactive({name: 'sandy',age: 18,a: {b: 19},cc: {dd: 76}})
let {age,a,cc} = r1;
age = 99;
a = {b: 20};
cc.dd = 53
console.log(r1) // { name: 'sandy', age: 18, a: { b: 19 }, cc: { dd: 53 } }
class ObjectRefImpl {
constructor(object,key) {
this.object = object;
this.key = key
}
get value() {
return this.object[this.key];
}
set value(newValue) {
this.object[this.key] = newValue;
}
}
function toRef(object,key) {
return new ObjectRefImpl(object,key)
}
function toRefs(object) {
const result = isArray(object)? new Array(object.length): {};
for(let key in object) {
result[key] = toRef(object,key)
}
return result
}
测试代码
let r1 = reactive({name: 'sandy',age: 18,a: {b: 19},cc: {dd: 76}});
let {age} = toRefs(r1);
age.value = 20;
console.log(r1) // { name: 'sandy', age: 20, a: { b: 19 }, cc: { dd: 76 } }
封装思路
因为前面提到,解构后的变量重新赋值响应性链接丢失,所以toRef的核心思想是在封装方法里面让其使用r1.name的形式进行访问和赋值,进而访问和赋值时会调用Proxy,所以源对象也会跟着更改。
访问和赋值时都需调用toRefs的value属性。
toRef
当我们只需要用到对象的某个属性时,可以用toRef(r1,‘age’)的形式去使用,原理同toRefs。