前言
本文主要对vue3.x中的ref进行模拟实现。
一. 完整代码
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
}
console.log(target,'get',key)
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) {
console.log(target,'set',key)
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
}
function toReactive(value) {
return isObject(value)?reactive(value):value
}
class RefImpl {
_value;
—isRef = true
constructor(rawValue) {
this._value = toReactive(rawValue)
}
get value() {
return this._value
}
set value(newValue) {
if(newValue !== this.rawValue) {
this._value = toReactive(newValue);
this.rawValue = newValue
}
}
}
function ref(value) {
return new RefImpl(value)
}
二. 封装思路
官网原话: reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref。
当ref包裹的是基础数据类型时,实现响应式的原理是Object.defineProperty的getter和setter,当为复杂数据类型时,则利用proxy,原理同reactive。响应式返回的值保存在value属性中。