vue3 响应式笔记

182 阅读7分钟

看视频瞎写的! 参考了各自各样的信息, 所以有点四不像 :(

订阅者模式

let activeEffect;

class Dep {
  constructor(val) {
    this._value = val;
    this.subscribers = new Set(); // 避免收集相同的依赖
  }

  get value() {
    this.depend(); // 收集当前依赖

    return this._value;
  }

  set value(newValue) {
    this._value = newValue;

    this.notify(); // 发布消息
  }

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }

  notify() {
    this.subscribers.forEach((effect) => {
      effect();
    });
  }
}

function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}

// 使用
const dep = new Dep(10);
let b;

watchEffect(() => {
  // 这里依赖 dep 的 value
  // 因此调用 Dep 的 set 收集这个依赖(activeEffect)
  // 由于都是同步的, 所以依赖会一个一个收集
  b = dep.value + 1;
});

watchEffect(() => {
  // 这里什么都不依赖
  console.log("不被收集");
});

dep.value = 11;
console.log(b); // 12

dep.value = 12;
console.log(b); // 13

defineProperty

let activeEffect;

class Dep {
  subscribers = new Set();

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }

  notify() {
    this.subscribers.forEach((effect) => {
      effect();
    });
  }
}

function reactive(raw) {
  Object.keys(raw).forEach((key) => {
    const dep = new Dep(); // 每个属性都有各自的 dep
    let value = raw[key];

    // 给 raw 的每个属性绑定 getter 和 setter
    Object.defineProperty(raw, key, {
      get() {
        dep.depend();

        return value;
      },

      set(newValue) {
        value = newValue;
        dep.notify();
      },
    });
  });

  return raw;
}

function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}

// 使用
const a = reactive({
  age: 10,
});

let b;

watchEffect(() => {
  b = a.age + 1;
});

a.age = 11;
console.log(b);

a.age = 12;
console.log(b);

image.png

Proxy

defineProperty 不太一样, gettersetter 根据 targetkey 判断, 因此需要有一个地方保存每个属性的 dep

const reactiveHandler = {
  get(target, key) {},

  set(target, key, value) {},
};

function reactive(raw) {
  return new Proxy(raw, reactiveHandler);
}
let activeEffect;

class Dep {
  subscribers = new Set();

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }

  notify() {
    this.subscribers.forEach((effect) => {
      effect();
    });
  }
}

const targetMap = new WeakMap();

function getDep(target, key) {
  let depsMap = targetMap.get(target);

  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  let dep = depsMap.get(key);

  if (!dep) {
    dep = new Dep();
    // 将响应式对象存储在 map 中
    depsMap.set(key, dep);
  }

  return dep;
}

const reactiveHandler = {
  get(target, key, receiver) {
    const dep = getDep(target, key);

    dep.depend();

    // 类似于 target[key].call(receiver), 为什么后面讲
    return Reflect.get(target, key, receiver);
  },

  set(target, key, value, receiver) {
    const dep = getDep(target, key);

    // 先设置新值再通知
    const res = Reflect.set(target, key, value, receiver);
    dep.notify();

    return res;
  },
};

function reactive(raw) {
  return new Proxy(raw, reactiveHandler);
}

function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null; // 用过一次后就清掉
}

// 使用
const a = reactive({
  age: 10,
});

let b;

watchEffect(() => {
  b = a.age + 1;
});

a.age = 11;
console.log(b);

a.age = 12;
console.log(b);

console.log(a);

image.png

上面写法在 effect 中有 setter 又有 getter 时会造成栈溢出

watchEffect(() => {
  a.age = 10;
  b = a.age + 1;
}); 

a.age = 11;

image.png

因为 setter 时会执行所有相关的 effect 里的函数, 而执行所有 effect 中函数又会调用 setter, 如此反复...

解决办法在 响应式 effect

Proxy 搭配 Reflect 使 this 总是指向 proxy

const obj = {
  name: "obj",
  get getName() {
    console.log(this === obj); // true
    return this.name;
  },
};

const proxy = new Proxy(obj, {
  get(target, key) {
    return target[key];
  },

  set(target, key, value) {
    target[key] = value;
  },
});

console.log(proxy.getName);
// proxy.getName 走了一次代理
// this.name 不走代理, 因为 this == obj, 不等于 proxy
const obj = {
  name: "obj",
  get getName() {
    console.log(this === proxy); // true
    return this.name;
  },
};

const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log("走了代理");
    return Reflect.get(target, key, receiver);
  },

  set(target, key, value, receiver) {
    return Reflect.set(target, key, value, receiver);
  },
});

console.log(proxy.getName);
// proxy.getName 走了一次代理
// this.name 又走了一次代理

// result:
// 走了代理
// true
// 走了代理
// obj

深度代理

const reactiveHandler = {
  get(target, key, receiver) {
    // ...
    const res = Reflect.get(target, key, receiver);

    // 只有 get 的时候才会代理
    if (typeof res === 'object' && res !== null) {
      return reactive(res); // 是对象, 把对象做成响应式
    }
    
    return res;
  },
};

判断是否是响应式对象

在第一次创建代理对象时, 给 target 绑定上特定标识

const enum ReactiveFlags {
  IS_REACTIVE = "_v_isReactive", // 响应式标识
}

const targetMap = new WeakMap();

function isReactive(target) {
  return !!target[ReactiveFlags.IS_REACTIVE];
}

function reactive(raw) {
  // 代理过了直接返回
  if (isReactive(raw) || !!targetMap.get(raw)) return raw;

  return new Proxy(raw, {
    get(target, key /*  */) {
      // 只有代理对象上这个 key 是 true
      if (key === ReactiveFlags.IS_REACTIVE) return true;

      // 其他逻辑...
    },
  });
}

响应式 effect

export class Dep {
  subscribers = new Set();

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }

  notify() {
    this.subscribers.forEach((effect) => {
      // 判断当前正在执行的 activeEffect 是否为 effect
      // 避免因为 setter 而反复执行这个 effect
      if (activeEffect !== effect) {
        effect.run();
      }
    });
  }
}
export let activeEffect = null;

export class ReactiveEffect {
  constructor(fn) {
    this.active = true;
    this.fn = fn;
  }

  run() {
    if (!this.active) {
      // 此时 activeEffect == null
      // 所以只执行 fn 而不会收集依赖
      return this.fn();
    }

    // 默认 active 为 true, 要收集依赖

    activeEffect = this; // 将全局副作用函数变为 this
    // 此时 activeEffect 不是 null 了, 就会被收集到
    const result = this.fn();

    activeEffect = null; // 用过一次后就清空

    return result;
  }
}

export function effect(fn) {
  const _effect = new ReactiveEffect(fn);

  _effect.run(); // 默认执行一次
}

上面将 activeEffect 清空会出现问题

这样不支持嵌套操作

effect(() => {
  b = a.age + 1;

  effect(() => {
    c = a.month + 2;
  }); // 由于这个结束后 activeEffect 变成 null 了

  d = a.year + 3; // 这个依赖就不会被收集了
});

因此使用一个 parent 节点保存上一次的 activeEffect

run() {
    // ...
    this.parent = activeEffect; // 保存前一次依赖
    activeEffect = this;
    const result = this.fn();
    activeEffect = this.parent; // 恢复前一次依赖
    // ...
}

依赖收集优化

const a = reactive({
  age: 10,
  count: 1,
  flag: true,
});

let b;

effect(() => {
  console.log("执行");
  // flag 为 true 时只收集 flag 和 age 依赖
  // flag 为 false 时只收集 flag 和 count 依赖
  b = a.flag ? a.age + 1 : a.count + 1;
});

console.log(b);

a.flag = false;

console.log(b);

setTimeout(() => {
  a.age = 1;
}, 1000);

// result:
// 执行
// 11
// 执行
// 2
// (1s 后)
// 执行 => 这说明 age 的 effect 还会执行:(

使用数组保存依赖对应的响应式属性

export class ReactiveEffect {
  constructor(fn) {
    this.active = true;
    this.fn = fn;
    this.deps = []; // 保存该副作用函数涉及到的所有响应式属性
  }
  
  // ...
}

这样在进行依赖追踪的时候保存一下

// getter
const dep = getDep(target, key);
dep.depend(); // 响应式属性收集依赖
activeEffect.deps.push(dep.subscribers); // 对应依赖保存响应式属性

在执行依赖收集前清空之前的依赖(双向清理)

// 这里也可以写成独立于 ReactiveEffect 的函数
export class ReactiveEffect {
  // ...
  cleanupEffect() {
    this.deps.forEach((dep) => {
      dep.delete(this); // dep 是一个 Set
    });

    effect.deps.length = 0; // 把 deps 清空
  }
}
export class ReactiveEffect {
  run() {
    // 其他逻辑...
    this.cleanupEffect(); // 收集前清空之前的
    const result = this.fn();
  }
}

但是这需要改变通知函数

export class Dep {
  // ...
  notify() {
    // 使用临时的 temp 执行通知
    // 因为 cleanupEffect 会清掉 subscribers 里的某个 effect
    // 但是再调用 this.fn() 后又会重新收集到 subscribers 里
    // 删一个又加一个, 就会导致无限循环
    // 所以删除前保存一个快照
    const temp = new Set(this.subscribers);
    temp.forEach((effect) => {
      if (activeEffect !== effect) {
        effect.run();
      }
    });
  }
}

现在在在 setTimeout 里改 a.age 就不会再执行 effect

调度器

export function effect(fn) {
  const _effect = new ReactiveEffect(fn);

  _effect.run(); // 默认执行一次

  const runner = _effect.run.bind(_effect);

  runner.effect = _effect;

  return runner;
}
let b = 0;

const runner = effect(() => {
  b = b + 1;
});

console.log(b); // 1

runner.effect.stop(); // 停止依赖收集

setTimeout(() => {
  runner(); // 手动决定执行时机
  console.log(b); // 2
}, 1000);

使用调度器 scheduler 优化

// 新增 options 选项
export function effect(fn, options = {}) { 
  const _effect = new ReactiveEffect(fn, options.scheduler);
  // ...
}
export class ReactiveEffect {
  constructor(fn, scheduler) {
    // 新增 scheduler
    this.scheduler = scheduler;
  }
}
notify() {
    const temp = new Set(this.subscribers);
    temp.forEach((effect) => {
      if (activeEffect !== effect) {
        // 如果有调度函数, 则执行调度函数
        if (effect.scheduler) {
          effect.scheduler();
        } else {
          effect.run();
        }
      }
  });
}
// 使用
const runner = effect(
  () => {
    console.log(a.age);
  },
  {
    scheduler() {
      setTimeout(() => {
        runner();
      }, 1000);
    },
  }
);

a.age = 100;

setTimeout(() => {
  a.age = 111; // 异步操作
}, 1000);

计算属性

export class ComputedRefImpl {
  constructor(getter, setter) {
    this._dirty = true; // 默认脏数据, 取值时计算
    this.setter = setter;
    
    // 使用 effect 收集依赖
    this.effect = new ReactiveEffect(getter, (/* 调度器 */) => {
      // 这个调度器是 computed 所用到的属性调用的
      // 也就是如果有某个响应式属性执行了其 setter, 就执行这个调度器
      // 将 computed 的数据变成脏数据
      // 当 computed 的属性被 getter 时, 就重新 run 获取新值, 实现了缓存
      if (!this._dirty) {
        this._dirty = true;
      }
    });
  }

  get value() {
    // 说明数据是脏的, 需要重新 run 获取新值
    if (this._dirty) {
      this._dirty = false;
      this._value = this.effect.run(); // 执行用户提供的 getter
    }

    return this._value;
  }

  set value(n) {
    this.setter(n);
  }
}

export function computed(options) {
  if (typeof options === "function") {
    // 只有 getter
    return new ComputedRefImpl(options, () => {});
  } else {
    // 对象
    return new ComputedRefImpl(options.get, options.set);
  }
}
// 使用
const name = reactive({
  first: "xiao",
  last: "ming",
});

const full = computed(() => {
  return name.first + " " + name.last;
});

console.log(full.value); // xiao ming

name.last = "hong";

console.log(full.value); // xiao hong

watch

export function watch(source, cb) {
  let getter;

  if (isReactive(source)) {
    // 如果是响应式数据
    // 递归遍历各个属性, 调用各属性的 getter 触发依赖收集
    getter = () => traversal(source);
  } else if (typeof source === "function") {
    // 如果是函数, 直接作为 getter
    getter = source;
  }

  let oldValue;

  const job = (/* 调度器 */) => {
    // 当被监控的数据调用 setter 后执行获取新值
    const newValue = effect.run();
    cb(newValue, oldValue); // 执行回调函数
    oldValue = newValue;
  };

  const effect = new ReactiveEffect(getter, job);
  oldValue = effect.run();
}
function traversal(value, set = new Set()) {
  // 不是对象, 直接返回
  if (typeof value !== "object") return value;

  // 已经访问过了, 直接返回
  if (set.has(value)) return value;

  set.add(value);

  for (let key in value) {
    traversal(value[key], set);
  }

  return value;
}
// 使用
const name = reactive({
  first: "xiao",
  last: "ming",
});

watch(
  () => name.last,
  (n, o) => {
    console.log(n, o);
  }
);

setTimeout(() => {
  name.last = "hong";
}, 1000);
// hong ming

ref

将一个值变成响应式

function convert(value) {
  // 如果是对象则使用 reactive 处理一下
  return typeof value === "object" ? reactive(value) : value;
}
// effect.js
// 依赖收集
export function trackEffects(dep) {
  // 依赖是否收集过, 收集过就不要再收集
  if (!!activeEffect && !dep.has(activeEffect)) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep);
  }
}
// effect.js
// 触发依赖
export function triggerEffects(dep) {
  const temp = new Set(dep);
  for (const effect of temp) {
    if (effect !== activeEffect) {
      if (effect.scheduler) {
        effect.scheduler();
      } else {
        effect.run();
      }
    }
  }
}
function convert(value) {
  return typeof value === "object" ? reactive(value) : value;
}

export class RefImpl {
  constructor(raw) {
    this._rawValue = raw;
    this._value = convert(raw); // 如果是对象, 转换成响应式
    this.deps = new Set();
  }

  get value() {
    trackEffects(this.deps); // 收集依赖
    return this._value;
  }

  set value(newValue) {
    if (newValue !== this._rawValue) {
      this._value = convert(newValue);
      this._rawValue = newValue;
      triggerEffects(this.deps); // 触发依赖
    }
  }
}

export function ref(value) {
  return new RefImpl(value);
}
// 使用
const name = ref(false);

effect(() => {
  // 会触发 ref 的 getter 并收集依赖
  console.log(name.value); 
});

name.value = true;

是否为 ref

export class RefImpl {
  __v_isRef = true; // 只要是 ref 对象就有这个静态属性
}
export function isRef(value) {
  return !!value.__v_isRef;
}

去 ref

export function unRef(ref) {
  return isRef(ref) ? ref.value : ref;
}

toRefs

class ObjectRefImpl {
  constructor(obj, key) {
    this.obj = obj;
    this.key = key;
  }

  get value() {
    return this.obj[this.key];
  }

  set value(newValue) {
    this.obj[this.key] = newValue;
  }
}

function toRef(object, key) {
  return new ObjectRefImpl(object, key);
}

export function toRefs(object) {
  let res;

  if (typeof object === "array") {
    res = new Array(object.length);
  } else {
    res = {};
  }

  for (let key in object) {
    // 把对象或数组里的每一个属性取出来
    // 封装成有 getter 和 setter 的对象
    // 其中的 getter 和 setter 直接调用属性的 getter 和 setter
    // 不能直接 res[key] = object[key] 
    // 因为属性是一个 proxy, 没有 value 属性, 不是 ref
    res[key] = toRef(object, key);
  }

  return res;
}