Other – NaN-1 VUE 响应式系统

基础分析

监听数据的读取和修改

js中有两个方法可以监听对象:

  • defineProperty 监听范围比较窄,兼容性较好,VUE2.0
  • Proxy 监听范围广,所有对象的内部方法都可以监听,但仅支持支持ES6的浏览器,VUE3.0
    因此,数据都需为对象
// reactive.js
import { track, trigger } from './effect.js';
// 将一个数据变为响应式数据
export function reactive(target) {
    return new Proxy(target, {
        get(target, key) {
            // 依赖收集:找到当前key对应的依赖
            track(target, key);
            // 返回值
            return Reflect.get(target, key);
        },
        set(target, key, value) {
            // 派发更新
            trigger(target, key);
            // 设置值
            return Reflect.set(target, key, value);
        }
    });
}

reactive函数将数据变为响应式数据

// effect.js

// Description: 依赖收集
export function track(target, key) {
    // 依赖收集
    console.log(`%c依赖收集:${key}`, 'color: red');
}

// Description: 派发更新
export function trigger(target, key) {
    // 派发更新
    console.log(`%c派发更新:${key}`, 'color: blue');
}

测试

// index.js
import { reactive } from "./reactive.js";

const state = reactive({
    harvester: 2,
    builder: 5,
    upgrader: 5
});

function fn() {
    state.harvester;
    state.builder;
}

fn();

state.upgrader = 3;
state.builder--; // (*)

结果如下:

Pasted image 20240706173253.png

fn 中引用了 harvester 和 builder 触发了依赖收集。
更改upgrader的值触发了派发更新。

(*) 行的 -- 先读取再赋值。因此都触发了。

监听对象数据读取和修改

优化

// reactive.js
import { isObject } from './utils.js';
import { handlers } from './handlers.js';

const target_map = new WeakMap();

// 将一个数据变为响应式数据
export function reactive(target) {
    // 不是对象,直接返回
    if (!isObject(target)) {
        return target;
    }
    // 同一个对象, 直接返回
    if (target_map.has(target)) {
        return target_map.get(target);
    }
    // 创建代理对象
    const proxy = new Proxy(target, handlers);
    // 缓存
    target_map.set(target, proxy);
    return proxy;
}
// handlers.js
import { track, trigger } from './effect.js';

export const handlers = {
    get(target, key) {
        // 依赖收集:找到当前key对应的依赖
        track(target, key);
        // 返回值
        return Reflect.get(target, key);
    },
    set(target, key, value) {
        // 派发更新
        trigger(target, key);
        // 设置值
        return Reflect.set(target, key, value);
    }
}

读取

在上面的代码中若对象使用了 getter 或 setter 会发生以下情况:

// index.js
import { reactive } from "./reactive.js";

const state = reactive({
    harvester: 2,
    builder: 5,
    upgrader: 5,
    get total() {
        return this.harvester + this.builder + this.upgrader;
    }
});

function fn() {
    state.total;
}

fn();
// 输出:依赖收集:total

我们希望的是应该也监听到 total 中使用的属性
在内部访问方法中,还有一个 receiver 可以指定 this 指向:

// handler.js
import { track, trigger } from './effect.js';

export const handlers = {
    get(target, key, receiver) {
        // 依赖收集:找到当前key对应的依赖
        track(target, key);
        // 返回值
        return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
        // 派发更新
        trigger(target, key);
        // 设置值
        return Reflect.set(target, key, value, receiver);
    }
}

于是:

Pasted image 20240706181415.png

可以监听到所有属性了

但是!
考虑以下情况:

import { reactive } from "./reactive.js";

const state = reactive({
    harvester: 2,
    builder: 5,
    upgrader: 5,
    spawn_point: {
        x: 0,
        y: 0
    }

});

function fn() {
    state.spawn_point.x;
    state.spawn_point.y;
}

fn();
// 输出:
// 依赖收集:spawn_point
// 依赖收集:spawn_point

为什么没有 x 和 y 呢?
在 handler 中,若 Reflect.get 获取到的是一个对象,被直接返回,并没有被代理,也就无法监听。

// handler.js
import { track, trigger } from './effect.js';
import { isObject } from './utils.js';
import { reactive } from './reactive.js';

export const handlers = {
    get(target, key, receiver) {
        // 依赖收集:找到当前key对应的依赖
        track(target, key);
        // 返回值
        const result = Reflect.get(target, key, receiver);
        // 如果是对象,递归代理
        return isObject(result) ? reactive(result) : result;
    },
    set(target, key, value, receiver) {
        // 派发更新
        trigger(target, key);
        // 设置值
        return Reflect.set(target, key, value, receiver);
    }
}

判断返回值是否是对象,若是则代理后返回。
Pasted image 20240706183355.png

但是(梅开二度)
考虑以下情况:

import { reactive } from "./reactive.js";

const state = reactive({
    harvester: 2,
    builder: 5,
    upgrader: 5
});

function fn() {
    "carrier" in state;
}

fn();

此时不会触发收集,因此加入 has 方法

// handler.js
import { track, trigger } from './effect.js';
import { isObject } from './utils.js';
import { reactive } from './reactive.js';

export const handlers = {
    get(target, key, receiver) {
        // 依赖收集:找到当前key对应的依赖
        track(target, key);
        // 返回值
        const result = Reflect.get(target, key, receiver);
        // 如果是对象,递归代理
        return isObject(result) ? reactive(result) : result;
    },
    has(target, key) {
        // 依赖收集:找到当前key对应的依赖
        track(target, key);
        // 返回值
        return Reflect.has(target, key);
    },
    set(target, key, value, receiver) {
        // 派发更新
        trigger(target, key);
        // 设置值
        return Reflect.set(target, key, value, receiver);
    }

}

还有吗,有,遍历,加入即可。

加入 type

在前面的代码中,若使用 in 检查了键

function fn() {
    "carrier" in state;
}

当对应键已经存在时,后续的改变,我们不希望此时认为需要触发更新,于是加入操作 type,方便后续操作。

// effect.js
// Description: 依赖收集
export function track(target, type, key) {
    // 依赖收集
    console.log(`%c依赖收集:[${type}] ${key}`, 'color: red');
}

// Description: 派发更新
export function trigger(target, type, key) {
    // 派发更新
    console.log(`%c派发更新:[${type}] ${key}`, 'color: blue');
}
// handler.js
import { track, trigger } from './effect.js';
import { isObject } from './utils.js';
import { reactive } from './reactive.js';
import { TrackOpTypes, TriggerOpTypes } from './operations.js';

export const handlers = {
    get(target, key, receiver) {
        // 依赖收集:找到当前key对应的依赖
        track(target, TrackOpTypes.GET, key);
        // 返回值
        const result = Reflect.get(target, key, receiver);
        // 如果是对象,递归代理
        return isObject(result) ? reactive(result) : result;
    },
    has(target, key) {
        // 依赖收集:找到当前key对应的依赖
        track(target, TrackOpTypes.HAS, key);
        // 返回值
        return Reflect.has(target, key);
    },
    set(target, key, value, receiver) {
        // 派发更新
        // TODO: 判断操作类型
        trigger(target, TriggerOpTypes.SET, key);
        // 设置值
        return Reflect.set(target, key, value, receiver);
    }

}

可以看到获取到了更详细的信息,方便后续操作。
Pasted image 20240706205535.png

优化...

// hanlder.js
import { track, trigger } from './effect.js';
import { isObject } from './utils.js';
import { reactive } from './reactive.js';
import { TrackOpTypes, TriggerOpTypes } from './operations.js';

function get(target, key, receiver) {
    // 依赖收集:找到当前key对应的依赖
    track(target, TrackOpTypes.GET, key);
    // 返回值
    const result = Reflect.get(target, key, receiver);
    // 如果是对象,递归代理
    return isObject(result) ? reactive(result) : result;
}

function has(target, key) {
    // 依赖收集:找到当前key对应的依赖
    track(target, TrackOpTypes.HAS, key);
    // 返回值
    return Reflect.has(target, key);
}

function set(target, key, value, receiver) {
    // 派发更新
    // TODO: 判断操作类型
    trigger(target, TriggerOpTypes.SET, key);
    // 设置值
    return Reflect.set(target, key, value, receiver);
}

function ownKeys(target) {
    // 派发更新
    track(target, TrackOpTypes.ITERATE);
    // 返回值
    return Reflect.ownKeys(target);
}

export const handlers = {
    get,
    has,
    set,
    ownKeys,
}

修改

区分 add 与 set

function set(target, key, value, receiver) {
    const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD;
    // 派发更新
    trigger(target, type, key);
    // 设置值
    return Reflect.set(target, key, value, receiver);
}

加入 delete,加入一些判断

// handler.js
import { track, trigger } from './effect.js';
import { isObject, isObject } from './utils.js';
import { reactive } from './reactive.js';
import { TrackOpTypes, TriggerOpTypes } from './operations.js';

function get(target, key, receiver) {
    // 依赖收集:找到当前key对应的依赖
    track(target, TrackOpTypes.GET, key);
    // 返回值
    const result = Reflect.get(target, key, receiver);
    // 如果是对象,递归代理
    return isObject(result) ? reactive(result) : result;
}

function has(target, key) {
    // 依赖收集:找到当前key对应的依赖
    track(target, TrackOpTypes.HAS, key);
    // 返回值
    return Reflect.has(target, key);
}

function set(target, key, value, receiver) {
    const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD;
    
    const oldValue = target[key];
    const result = Reflect.set(target, key, value, receiver);

    if (result && (hasChanged(value, oldValue) || type === TriggerOpTypes.ADD)) {
        // 派发更新
        trigger(target, type, key);
    }
    // 设置值
    return result;
}

function deleteProperty(target, key) {
    const hadKey = target.hasOwnProperty(key);
    // 删除值
    const result = Reflect.deleteProperty(target, key);
    if (hadKey && result) {
        // 派发更新
        trigger(target, TriggerOpTypes.DELETE, key);
    }
    return result;
}

function ownKeys(target) {
    // 依赖收集
    track(target, TrackOpTypes.ITERATE);
    // 返回值
    return Reflect.ownKeys(target);
}

export const handlers = {
    get,
    has,
    set,
    ownKeys,
    deleteProperty
}

测试代码:

// index.js
import { reactive } from "./reactive.js";

const state = reactive({
    harvester: 2,
    builder: 5,
    upgrader: 5
});

function fn() {
    Object.keys(state);

}

fn();

state.builder = +0; // (*)
state.carrier = 3; // (**)

state.builder = -0; // (***)
state.builder = -0; // (****)

delete state.carrier;
delete state.carrier;

结果:

Pasted image 20240706222136.png

让我们来分析一下:

  • fn 函数中使用Object.keys返回对象的所有键,触发了ownKeys,进行依赖收集。
  • (*) 行更改 builder 值,5 => +0 值变化,触发set。
  • (**) 行添加 carrier 属性,触发add。
  • (***) 行更改 builder 值,+0 => -0 值变化,触发set。
  • (****) 行值未变化,未触发set。
  • 最后两行,第一行删除 carrier 属性,触发delete,第二行已经没有 carrier 属性,不再触发delete。

严格等于还是 Object.is?
+0 === -0 结果为 true,NaN === NaN 结果为 false。
Object.is(-0, +0) 结果为 false,Object.is(NaN, NaN) 结果为 true。
我们需要的是判断新值是否会改变函数的运行结果,因此我们使用Object.is。

监听数组数据读取和修改

读取

尝逝

现在让我们考虑一下响应式数据是数组的情况。

import { reactive } from "./reactive.js";

const state = reactive([1, 2, 3, 4, 5]);

function fn() {
	console.log('0---------');
    state[0];
    state.length;

    console.log('1---------');
    for (let i = 0; i < state.length; i++) {
        state[i];
    }

    console.log('2---------');
    state.includes(3);

    console.log('3---------');
    state.indexOf(2);

    console.log('4---------');
    for (let key of state) {
        state[key];
    }
}

fn();

让我们依次来看一下结果:
第零部分,正常获取,正确收集

Pasted image 20240707150446.png

第一部分,使用for循环遍历,每次获取length以及下标,有重复,但暂时不用处理。
Pasted image 20240707150559.png

第二部分,先获取到includes属性,然后运行函数,从0开始取出数据与3比较,找到下标为2时相等,返回结果。收集正确。
Pasted image 20240707150727.png

第三部分,访问indexOf,然后length,发现使用了has,indexOf会先检查是否有这个键,然后再取出访问。
Pasted image 20240707151258.png

第四部分,啊嘞,没输出?
Pasted image 20240707151452.png

查看报错信息,发现将Symbol键转换为string失败,更改effect.js:

// effect.js
import { TrackOpTypes, TriggerOpTypes } from "./operations.js";

// Description: 依赖收集
export function track(target, type, key) {
    // 依赖收集
    if (type === TrackOpTypes.ITERATE) {
        console.log(`%c依赖收集:[${type}]`, 'color: red');
    }
    else {
        console.log(`%c依赖收集:[${type}] `, 'color: red', key);
    }
}

// Description: 派发更新
export function trigger(target, type, key) {
    // 派发更新
    console.log(`%c派发更新:[${type}] `, 'color: blue', key);
}

结果:
Pasted image 20240707152024.png

为什么要判断数组有没有下标
存在稀疏数组:[0, , , 2],根据我们上面发现的逻辑,若我们试图在数组中找undefined,会返回下标1吗?
不会,因为会先判断键是否存在,不存在将直接跳过,不会有后续比较。

看起来不错,是吗?

数组中的对象

看看这个:

import { reactive } from "./reactive.js";

const obj = {};
const state = reactive([1, obj, 3]);

function fn() {
    console.log(state.indexOf(obj));
}

fn();

能找到吗?

Pasted image 20240707153224.png

没找到,为什么?
还记得我们上一章中的,对象中的对象如何处理的吗?
我们检测返回的是否是对象,若是将其代理,那么这里数组里的对象在访问时返回的是被代理后的对象,与原对象不同,那就找不到了。
两个解决方案:

  1. 传入的原始对象转换为代理对象。
  2. 代理对象中找不到,在原始数组中去重新找。(VUE采用的方案)
    方法一看起来更好,因此我们选择第二种。(欸嘿)

我们需要改动get方法,拦截获取的indexOf方法,返回我们更改后的方法。

// handlers.js
...
const arrayInstrumentations = {};
const RAW = Symbol('raw');

["includes", "indexOf", "lastIndexOf"].forEach(key => {
    arrayInstrumentations[key] = function(...args) {
        // 正常找从代理对象中找
        const res = Array.prototype[key].apply(this, args);
        // 若找不到,再在原始数组中找
        if (res === -1 || res === false) {
            return Array.prototype[key].apply(this[RAW], args);
        }
        return res;
    };
});

function get(target, key, receiver) {
    // 返回原始对象 (***)
    if (key === RAW) {
        return target;
    }
    // 依赖收集:找到当前key对应的依赖
    track(target, TrackOpTypes.GET, key);
    // 返回值
    if (arrayInstrumentations.hasOwnProperty(key) && Array.isArray(target)) {
        return arrayInstrumentations[key];
    }

    const result = Reflect.get(target, key, receiver);
    // 如果是对象,递归代理
    return isObject(result) ? reactive(result) : result;
}
...

当get这["includes", "indexOf", "lastIndexOf"]三个方法时,我们返回我们自定义的方法,先正常找,若找不到则获取原始对象再找。

(***)行,为了避免与传入的对象键冲突,使用Symbol避免冲突。

方法二带来的问题
在我们开头的示例中,我们查找obj,按照正常查找的逻辑,我们在下标1就应该找到了,我们不应该收集到下标2,但是如下图,我们却收集到了下标2。
Pasted image 20240707161136.png

修改

Length触发问题

看看这个

const obj = {};
const state = reactive([1, obj, 3, , 5]);

// 等同于 state[state.length] = 6;
// 即修改一个超过数组长度索引
state.push(6);

Pasted image 20240707165836.png

length呢?push后length也会变化,但这里并未出现length的变化
length的属性设置是通过defineProperty方法,我们使用下面的方法试试。

Object.defineProperty(state, 'length', 7);

并未输出length的修改,因此让我们添加监听。

首先分析一下什么情况下需要手动触发length属性变化

  1. 设置的对象是一个数组。
  2. 设置前后length发生了变化。
  3. 设置的不是length属性。
    上面三个条件同时满足就需要手动触发。
function set(target, key, value, receiver) {
    const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD;
    
    const oldValue = target[key];
    const oldLength = Array.isArray(target) ? target.length : undefined;
    // 设置值
    const result = Reflect.set(target, key, value, receiver);

    const newLength = Array.isArray(target) ? target.length : undefined;

    if (result && (hasChanged(value, oldValue) || type === TriggerOpTypes.ADD)) {
        // 派发更新
        trigger(target, type, key);
        // 数组长度变化
        if (Array.isArray(target) && key !== 'length' && newLength !== oldLength) {
            trigger(target, TriggerOpTypes.SET, 'length');
        }

    }
    return result;
}

结果:
Pasted image 20240707171013.png

还没完,当我们将length直接改小时:

state.length = state.length - 2;

Pasted image 20240707171315.png

length改小会删除属性,但并未监听到,因此继续判断:

function set(target, key, value, receiver) {
    const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD;
    
    const oldValue = target[key];
    const oldLength = Array.isArray(target) ? target.length : undefined;
    // 设置值
    const result = Reflect.set(target, key, value, receiver);

    const newLength = Array.isArray(target) ? target.length : undefined;

    if (result && (hasChanged(value, oldValue) || type === TriggerOpTypes.ADD)) {
        // 派发更新
        trigger(target, type, key);
        // 数组长度变化
        if (Array.isArray(target) && newLength !== oldLength) {
            if (key !== 'length') {
                trigger(target, TriggerOpTypes.SET, 'length');
            }
            else {
                // 找到被删除的元素,触发更新
                for (let i = newLength; i < oldLength; i++) {
                    trigger(target, TriggerOpTypes.DELETE, i + '');
                }
            }
        }

    }
    return result;
}

需要收集Length吗

还记得刚刚的例子吗

const obj = {};
const state = reactive([1, obj, 3, , 5]);

// 等同于 state[state.length] = 6;
// 即修改一个超过数组长度索引
state.push(6);

Pasted image 20240707165836.png

让我们看看收集部分,收集到push,没问题,如果push被改了结果会变,收集到length,似乎没问题。
但是考虑一下使用push的目的,是想添加一个元素进去,并不想收集length,
我们如果需要收集length,为什么不直接获取length呢?
因此,此处我们不想要收集length。
同样,有两个方法:

  1. 将会对数组产生改动的方法全部重写。
  2. 调用方法期间,暂停依赖收集。(VUE)
    第一种emmm,纯重写,问题很大,选第二种
// handlers.js
...
["push", "pop", "shift", "unshift", "splice"].forEach(key => {
    arrayInstrumentations[key] = function(...args) {
        pauseTracking(); // 暂停依赖收集
        const res = Array.prototype[key].apply(this, args);
        resumeTracking(); // 恢复依赖收集
        return res;
    };
});
...
// effect.js
...
let shouldTrack = false;

// Description: 暂停依赖收集
export function pauseTracking() {
    shouldTrack = false;
}

// Description: 恢复依赖收集
export function resumeTracking() {
    shouldTrack = true;
}

// Description: 依赖收集
export function track(target, type, key) {
    if (!shouldTrack) {
        // 不需要依赖收集
        return;
    }
    ...
}
...

当然还有许多细节
除了数组还有Map,Set等...

关联数据和函数

亿些处理

// effect.js
import { TrackOpTypes, TriggerOpTypes } from "./operations.js";

const targetMap = new WeakMap();

const ITERATE_KEY = Symbol('iterate');

let activeEffect;

export function effect(fn) {
    const effectfn = () => {
        try {
            activeEffect = effectfn;
            return fn();
        }
        finally {
            activeEffect = null;
        }
    }
    effectfn();
}

let shouldTrack = false;

// Description: 暂停依赖收集
export function pauseTracking() {
    shouldTrack = false;
}

// Description: 恢复依赖收集
export function resumeTracking() {
    shouldTrack = true;
}

// Description: 依赖收集
export function track(target, type, key) {
    if (!shouldTrack || !activeEffect) {
        // 不需要依赖收集
        return;
    }
    // 依赖收集
    if (type === TrackOpTypes.ITERATE) {
        console.log(`%c依赖收集:[${type}]`, 'color: red');
    }
    else {
        console.log(`%c依赖收集:[${type}] `, 'color: red', key);
    }
}

// Description: 派发更新
export function trigger(target, type, key) {
    // 派发更新
    console.log(`%c派发更新:[${type}] `, 'color: blue', key);
}

targetMap 结构:
targetMap

  • 对象1:propMap
  • 对象2:propMap
    • 属性1:typeMap
    • 属性2:typeMap
      • get:dep
      • has:dep
        • fn1
        • fn2

依赖收集

export function track(target, type, key) {
    if (!shouldTrack || !activeEffect) {
        // 不需要依赖收集
        return;
    }

    let propMap = targetMap.get(target);
    if (!propMap) {
        propMap = new Map();
        targetMap.set(target, propMap);
    }
    if (type === TrackOpTypes.ITERATE) {
        key = ITERATE_KEY;
    }
    let typeMap = propMap.get(key);
    if (!typeMap) {
        typeMap = new Map();
        propMap.set(key, typeMap);
    }
    let depSet = typeMap.get(type);
    if (!depSet) {
        depSet = new Set();
        typeMap.set(type, depSet);
    }
    if (!depSet.has(activeEffect)) {
        depSet.add(activeEffect);
    }
}

派发更新

export function trigger(target, type, key) {
    // 派发更新
    const effectfns = getEffectFns(target, type, key);
    if (effectfns) {
        for (const effectfn of effectfns) {
            effectfn();
        }
    }

    // console.log(`%c派发更新:[${type}] `, 'color: blue', key);
}

function getEffectFns(target, type, key) {
    const propMap = targetMap.get(target);
    if (!propMap) {
        return;
    }
    const keys = [key];
    if (type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
        keys.push(ITERATE_KEY);
    }
    const effectFns = new Set();
    const triggerTypeMap = {
        [TriggerOpTypes.SET]: [TrackOpTypes.GET],
        [TriggerOpTypes.ADD]: [TrackOpTypes.ITERATE, TrackOpTypes.HAS, TrackOpTypes.GET],
        [TriggerOpTypes.DELETE]: [TrackOpTypes.ITERATE, TrackOpTypes.HAS, TrackOpTypes.GET],
    };
    
    for (let key of keys) {
        const typeMap = propMap.get(key);
        if (!typeMap) {
            continue;
        }
        const trackTypes = triggerTypeMap[type];
        for (const trackType of trackTypes) {
            const depSet = typeMap.get(trackType);
            if (depSet) {
                depSet.forEach(effect => {
                    effectFns.add(effect);
                });
            }
        }
    }
    
    return effectFns;
}

亿些bug

一:

let state = reactive({
    a: 1,
    b: 2,
    c: 3
});

function fn1() {
    console.log('fn1 run');
    if (state.a === 3) {
        state.b;
    }
    else {
        state.c;
    }
}

effect(fn1);

console.log('---------');
state.c = 3;
console.log('---------');
state.a = 3;
console.log('---------');
state.b = 4;
console.log('---------');
state.c = 5;
console.log('---------');

当我们将a的值改了后,运行fn1,应该重新收集a,b,删除c,但是最后修改c发现函数又运行了。我们并未处理删除c。

二:

let state2 = reactive([1, 2, 3]);

function fn3() {
    console.log('fn3 run');
    effect(() => {
        console.log('inner run');
        state2[1];
    });
    state2[2];
}

effect(fn3);

console.log('---------');
state2[1] = 3;
console.log('---------');
state2[2] = 4;
console.log('---------');

修改下标2时应该触发fn3运行,但发现fn3并未运行。
我们只使用了一个activeEffect来储存,在内部函数运行后,activeEffect被设置为null,导致下标2未被收集。
执行栈问题。添加栈处理。

三:

let state2 = reactive([1, 2, 3]);

function fn4() {
    console.log('fn4 run');
    state2[1]++;
}

effect(fn4);

爆栈,判断一下即可。

四:

let state2 = reactive([1, 2, 3]);

function fn2() {
    console.log('fn2 run');
    if (state2.length > 3) {
        state2[2];
    }
    else {
        state2[1];
    }
}

effect(fn2);

console.log('---------');
state2[1] = 3;
console.log('---------');
state2.push(4);
console.log('---------');
state2[2] = 5;
console.log('---------');

按理说,修改下标2时,由于前面push,length大于3后,收集的依赖应该是下标2和length,但是最后修改下标2却没有触发更新。原因是push过程中暂停了依赖收集,导致并没有收集到下标2的依赖。

......

补充实现

ref

// ref.js
import { TrackOpTypes, TriggerOpTypes } from "./operations.js";
import { track, trigger } from "./effect.js";

export function ref(value) {
    return {
        get value() {
            track(this, TrackOpTypes.GET, "value");
            return value;
        },
        set value(newValue) {
            value = newValue;
            trigger(this, TriggerOpTypes.SET, "value", newValue);
        }
    };
}

测试:

import { reactive } from "./reactive.js";
import { effect } from "./effect.js";
import { ref } from "./ref.js";

const state = ref(1);

effect(() => {
    console.log("effect", state.value);
})

state.value++;

computed

// computed.js
import { effect } from "./effect.js";
import { track, trigger } from "./effect.js";
import { TrackOpTypes, TriggerOpTypes } from "./operations.js";

function normalizeParameters(getterOrOptions) {
    let getter, setter;
    if (typeof getterOrOptions === 'function') {
        getter = getterOrOptions;
        setter = () => {
            console.warn('computed() received a function but no setter.')
        }
    }
    else {
        getter = getterOrOptions.get;
        setter = getterOrOptions.set;
    }
    return { getter, setter };
}

export function computed(getterOrOptions) {
    const { getter, setter } = normalizeParameters(getterOrOptions);
    let value, dirty = true;
    const effectfn = effect(getter, { 
        lazy: true,
        scheduler: () => {
            dirty = true;
            trigger(computed, TriggerOpTypes.SET, 'value');
        }
    });
    return {
        get value() {
            track(computed, TrackOpTypes.GET, 'value');
            if (dirty) {
                value = effectfn();
                dirty = false;
            }
            return value;
        },
        set value(newValue) {
            setter(newValue);
        }
    }
}

测试:

import { reactive } from "./reactive.js";
import { effect } from "./effect.js";
import { ref } from "./ref.js";
import { computed } from "./computed.js";

const state = reactive({
    a: 1,
    b: 2,
    c: 3
});

const sum = computed(() => {
    console.log('computed run');
    return state.a + state.b + state.c;
});

sum.value;
sum.value;
sum.value;
sum.value;
啊哈,这里是小尾巴~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇