vue3.0(双向绑定)源码分析

vue3.0双向绑定相关的模块有reactive/effect/computed/ref,这篇文章主要聊聊我对它们的理解,以及我在编码中遇到的问题,希望能给大家带来帮助,首先,一张图来分析它们之间的依赖关系。


image.png

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;

项目案例

你可能感兴趣的:(vue3.0(双向绑定)源码分析)