vue3.0双向绑定相关的模块有reactive/effect/computed/ref,这篇文章主要聊聊我对它们的理解,以及我在编码中遇到的问题,希望能给大家带来帮助,首先,一张图来分析它们之间的依赖关系。
reactive
把数据转化成响应式的数据,获取/修改数据时,能数据进行劫持操作。vue3.0用的ES6的proxy,在此之前用的是Object.defineProperty。这里有两个问题需要注意:1/proxy不会对子对象进行劫持,需要递归。2/新增数据时,set会执行两次,是因为length属性也进行劫持。
import { isObjce ,hasOwn } from './utils.js';
import { track, trigger } from './effect';
function reactive(targetObj) {
//判断是否为对象
if (isObjce(targetObj)) {
let reactiveTarget = effect(targetObj);
return reactiveTarget;
} else {
return targetObj
}
}
function effect(targetObj) {
var obj = new Proxy(targetObj, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
// 获取value值,类似target[propKey]
let getValue = Reflect.get(target, propKey, receiver);
track(target,"get",propKey);
return isObjce(getValue)?reactive(getValue):getValue;
},
set: function (target, propKey, value, receiver) {
let oldValue = target[propKey];
// 修改value值,类似target[propKey] = value
let setValue = Reflect.set(target, propKey, value, receiver);
// 属性是否存在对象中
if(!hasOwn(target,propKey)){
console.log("更新添加");
trigger(target,"add",propKey);
}else if(oldValue!=value){
console.log("更新修改");
trigger(target,"set",propKey);
}
return setValue;
}
});
return obj;
}
export default reactive;
effect
利用高阶函数的思想,把effect弄成一个单独可用的函数体,在函数体内,执行回调函数并在外部进行一些逻辑处理。在effect文件中,还暴露了track/trigger。当effect中,存在依赖的数据,会调用reactive文件的get方法,get方法调用track,track函数会把storeEffect中的effect用一种格式存储起来。set方法会调用trigger,trigger会调用get存储的effect。
function effect(fn,option={}){
let effect = createEffect(fn,option);
if(!option.lazy){
effect();
}
return effect;
}
let storeEffect = [];
let uid = 0;
function createEffect(fn,option={}){
let effect = function(){
if (!storeEffect.includes(effect)) {
try{
storeEffect.push(effect);
return fn();
}finally{
storeEffect.pop();
}
}
};
effect.scheduler = option.scheduler || null;
effect.id = uid++;
return effect;
}
/**
* WeakMap的key值可以是对象,更方便用户处理
* tranck存储的格式: WeakMap { object : map { key : set [effect] } }
*/
let targetMap = new WeakMap();
function track(target,type,key){
let effect = storeEffect[storeEffect.length-1];
if(effect){
let depsmap = targetMap.get(target);
if(!depsmap){
targetMap.set(target,depsmap = new Map());
}
let depset = depsmap.get(key);
if(!depset){
depsmap.set(key,depset = new Set());
}
if(!depset.has(effect)){
depset.add(effect);
}
}
}
function trigger(target,type,key){
console.log("targetMap:",targetMap);
let depsmap = targetMap.get(target);
if(!depsmap){
return false;
}
let depset = depsmap.get(key);
if(depset){
depset.forEach(effect=>{
if(effect.scheduler){
effect.scheduler();
}else{
effect();
};
});
}
}
export { effect, track, trigger };
computed
依赖effect当数据改变时,触发当前的get回调函数,当获取返回值时,返回当前get回调函数的返回值,当设置时,触发set回调函数传递最新值。这里有点需要注意,这里的有个缓存机制,获取相同的值时,get回调函数值触发一次,通过dirty变量实现。
import { isObjce } from './utils.js';
import {effect} from './effect';
function computed(getter){
let dirty = true;
let getFun = () => {};
let setFun = () => {};
let value = null;
if(!isObjce(getter)){
getFun = getter;
}else{
getFun = getter.get;
setFun = getter.set;
};
let effectRun = effect(getFun,{
lazy:true,
scheduler:()=>{
dirty = true;
}
})
console.log("effectRun:",effectRun);
return {
get value(){
if(dirty){
value = effectRun();
dirty = false;
}
return value;
},
set value(value){
setter(value)
}
}
}
export default computed;
ref
绑定一个非对象类型的数据,直接通过返回对象的get/set方法实现。
import { isObjce } from './utils.js';
import reactive from "./reactive.js";
function ref(value){
let tranValue = isObjce(value)?reactive(value):value;
return {
_isRef:true,
get value(){
return tranValue;
},
set value(newValue){
tranValue = newValue;
}
};
}
export default ref;
项目案例