Skip to main content

Command Palette

Search for a command to run...

Vue3响应式

Published
2 min readView as Markdown
  • track() 保存代码
  • effect() 需要运行代码
  • trigger() 运行所有存储的代码
let dep = new Set(); 
let effect = ()=>{total=price * quantity}
function track(){dep.add(effect)}
function trigger(){dep.forEach(effect=>effect())}

引入depsMap(一个depsMap对应一个响应式对象),一个dep对应一个响应式对象上的属性

const depsMap = new Map();
function track(key){
    let dep = depsMap.get(key);
    if(!dep){
        depsMap.set(key,(dep = new Set()));
    }
    dep.add(effect);
}
function trigger(key){
    let dep = depsMap.get(key);
    if(dep){
        dep.forEach(effect=>effect())
    }
}

targetMap(weekMap,key是object。存储所有响应式对象)

const targetMap = new WeakMap();
function track(target,key){
    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(effect);
}
function trigger(target,key){
    let depsMap = targetMap.get(target);
    if(!depsMap){return}
    let dep = depsMap.get(key);
    if(dep){
        dep.forEach(effect=>effect())
    }
}

test

let product = {price:5,quantity:2}
let total = 0;
let effect = ()=>{
    total = product.price*product.quantity;
}
track(product,'quantity');
effect();
/*
> total //10
> trigger(product,'quantity')
> total //15
*/

question:如何自动执行?

响应式引擎

当我们运行effect,如果访问(get)了产品的属性,我们想track去保存effect,当我们(set)去改变产品的属性时,我们想用trigger去运行保存的effect

怎样去拦截GET,SET?(Proxy、reactive)

  • vue2 使用了ES5 的Object.defineProperty()
  • vue3 使用ES6 的Reflect 和Proxy
console.log(Reflect.get(Product,'quantity'))
function reactive(target){
    const handler = {
        get(target,key,receiver){
            let res = Reflect.get(target,key,receiver)//receiver能保证this指向正确
            track(target,key);
            return res;
        },
        set(target,key,value,receiver){
            let oldValue = target[key];
            let res = Reflect.set(target,key,value,receiver);
            if(oldValue!=res){
                trigger(target,key);
            }
             return res
        }
    }
    return new Proxy(target,handler)
}
let product = reactive({price:5,quantity:2})
let total = 0;
let effect = ()=>{
    total = product.price*product.quantity;
}
effect();
console.log(total);
product.quantity = 3;
console.log(total)

是否需要在所有地方的get上调用track?

activeEffect(优化)

仅在effect中读取时才记录

let activeEffect = null
function effect(eff){
    activeEffect = eff
    activeEffect()
    activeEffect = null
}
//..
effect(()=>{
    total = product.price*product.quantity
})
function track(target,key){
   if(activeEffect){
        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);
   }
}
let product = reactive({price:5,quantity:2})
let salePrice = 0;
let total = 0;
effect(()=>{
    total = product.price*product.quantity
})
effect(()=>{
    salePrice = product.price * 0.9
})
console.log(`before update total = ${total} salePrice = ${salePrice}`)
product.quantity = 3;
console.log(`after update total = ${total} salePrice = ${salePrice}`)
product.price = 10
console.log(`after update total = ${total} salePrice = ${salePrice}`)

把product.price换成salePrice怎么实现响应式呢?

Ref

salePrice = ref(0)
effect(()=>{
    total = salePrice.value*product.quantity
})
effect(()=>{
    salePrice.value = product.price * 0.9
})

利用reactive

function ref(intiaValue){
    return reactive({value:intiaValue})
}

利用js计算属性

function ref(raw){
    const r = {
        get value(){
            track(r,'value')
            return raw
        }
        set value(newVal){
            raw = newVal; 
            trigger(r,'value')
        }
    }
    return r;
}

computed

function computed(getter){
    let res = ref();
    effect(()=>{
        res.value = getter();
    })
    return res;
}

More from this blog

Es5原型与继承

prototype:函数对象的原型对象(普通对象) __proto__:指向创建它的构造函数的原型对象 原型链的连接是实例(car1)和构造函数(Car)的原型对象(Car.prototype)之间,而不是实例和构造函数之间 构造函数式继承 可以子传父 引用类型的值相互独立 父类构造函数重复执行,每次创建子类实例都会调用一次 脱离于原型链,父类原型链上的方法属性无法继承 function Car(name,price){ this.name = name; this.pric...

Oct 16, 20211 min read

前端模块化与包管理器

什么是模块化? 将一个复杂的程序依据一定的规则封装成几个块文件,并进行组合。 模块化特点 模块中数据与实现是私有的,只向外部暴露一些接口方法与其他模块通信 模块化历程 全局function function util1(){ //... } function util2(){ //... } 引入js文件,这样会污染到全局。当时使用命名空间优化这个问题,引入一个工具对象(不私有) 闭包 向window对象添加全局属性 (function (win){ ...

Oct 15, 20213 min read

抽象语法树相关

抽象语法树 大多数编译器分为三个主要阶段:解析,转换和代码生成 解析正在获取原始代码并将其转换为更抽象的代码 代码的表示形式。 转换采取这种抽象表示并操纵去做 编译器想要的任何东西。 代码生成采用代码的转换表示形式, 将其转换为新代码。 解析 解析通常分为两个阶段:词法分析和句法分析。 词法分析提取原始代码并将其拆分为标记器(或词法分析器)的事物称为标记。 标记器是由微小的小物体组成的数组,描述了一个孤立的片断的语法。它们可以是数字,标签,标点符号,运算符,任何。 句法分...

Oct 15, 20215 min read
K

kc‘s blog

9 posts