vue3 Ref 手写源码

98 阅读2分钟

禁止转载,侵权必究!

ref 实现原理

proxy 代理的目标必须是非原始值,所以 reactive 不支持原始值类型。所以我们需要将原始值类型进行包装。

6.ref.html script

const flag = ref(false);
effect(() => {
  document.body.innerHTML = flag.value ? 30 : "lx";
});
setTimeout(() => {
  flag.value = true;
}, 1000);

1.Ref & ShallowRef

ref.ts

function createRef(rawValue, shallow) {
  return new RefImpl(rawValue, shallow); // 将值进行装包
}
// 将原始类型包装成对象, 同时也可以包装对象 进行深层代理
export function ref(value) {
  return createRef(value, false);
}
// 创建浅ref 不会进行深层代理
export function shallowRef(value) {
  return createRef(value, true);
}
function toReactive(value) {
  // 将对象转化为响应式的
  return isObject(value) ? reactive(value) : value;
}
class RefImpl {
  public _value;
  public dep;
  public __v_isRef = true;
  constructor(public rawValue, public _shallow) {
    this._value = _shallow ? rawValue : toReactive(rawValue); // 浅ref不需要再次代理
  }
  get value() {
    if (activeEffect) {
      trackEffects(this.dep || (this.dep = new Set())); // 收集依赖
    }
    return this._value;
  }
  set value(newVal) {
    if (newVal !== this.rawValue) {
      this.rawValue = newVal;
      this._value = this._shallow ? newVal : toReactive(newVal);
      triggerEffects(this.dep); // 触发更新
    }
  }
}

2.toRef & toRefs

ref.ts

class ObjectRefImpl {
  public __v_isRef = true;
  constructor(public _object, public _key) {}
  get value() {
    return this._object[this._key];
  }
  set value(newVal) {
    this._object[this._key] = newVal;
  }
}
export function toRef(object, key) {
  // 将响应式对象中的某个属性转化成ref
  return new ObjectRefImpl(object, key);
}
export function toRefs(object) {
  // 将所有的属性转换成ref
  const ret = Array.isArray(object) ? new Array(object.length) : {};
  for (const key in object) {
    ret[key] = toRef(object, key);
  }
  return ret;
}

6.ref.html script

// let name = toRef(person, 'name')
// let age = toRef(person, 'age')

let { name, age } = toRefs(person);
let flag = ref(true);
watchEffect(() => {
  // 用户在模板中使用的时候 不用.value的原因就是 内部会将数据用proxyRefs
  // 做处理
  // name = person.name.value
  app.innerHTML = flag.value ? name.value : age.value;
});

setTimeout(() => {
  flag.value = !flag.value;
}, 1000);

3.自动脱 ref(proxyRefs)

6.ref.html script

let state = reactive({ name: "zf", age: 13 });
let person = proxyRefs({ ...toRefs(state) });
effect(() => {
  document.body.innerHTML = person.name + "今年" + person.age + "岁了";
});
setTimeout(() => {
  person.age = 31;
}, 1000);

ref.ts

export function proxyRefs(objectWithRefs) {
  // 代理的思想,如果是ref 则取ref.value
  return new Proxy(objectWithRefs, {
    get(target, key, receiver) {
      let v = Reflect.get(target, key, receiver);
      return v.__v_isRef ? v.value : v;
    },
    set(target, key, value, receiver) {
      // 设置的时候如果是ref,则给ref.value赋值
      const oldValue = target[key];
      if (oldValue.__v_isRef) {
        oldValue.value = value;
        return true;
      } else {
        return Reflect.set(target, key, value, receiver);
      }
    },
  });
}

4.effectScope 作用域

7.effectScope.html js

const scope = effectScope();

// 外层的作用域应该收集里层作用域
scope.run(() => {
  const state = reactive({ age: 30 });
  let runner = effect(() => {
    console.log(state.age);
  });
  setTimeout(() => {
    state.age++;
  }, 2000);
  const scope = effectScope(true);
  scope.run(() => {
    const state = reactive({ age: 35 });
    let runner = effect(() => {
      console.log(state.age);
    });
    setTimeout(() => {
      state.age++;
    }, 2000);
  });
});

scope.stop();

effectScope.ts

export let activeEffectScope;
class EffectScope {
  active = true;
  effects = []; // 这个是收集内部的effect
  parent;
  scopes; // 这个是收集作用域的
  constructor(detached = false) {
    // detached 是不是独立的 effectScope
    if (!detached && activeEffectScope) {
      activeEffectScope.scopes || (activeEffectScope.scopes = []).push(this);
    }
  }
  run(fn) {
    if (this.active) {
      try {
        this.parent = activeEffectScope;
        activeEffectScope = this;
        return fn();
      } finally {
        // 清空了
        activeEffectScope = this.parent;
        this.parent = null;
      }
    }
  }
  stop() {
    if (this.active) {
      for (let i = 0; i < this.effects.length; i++) {
        this.effects[i].stop(); // 让每一个存储的effect全部终止
      }
    }
    if (this.scopes) {
      for (let i = 0; i < this.scopes.length; i++) {
        this.scopes[i].stop(); // 调用的是作用域的stop
      }
    }
    this.active = false;
  }
}
export function recordEffectScope(effect) {
  if (activeEffectScope && activeEffectScope.active) {
    activeEffectScope.effects.push(effect);
  }
}
export function effectScope(detached) {
  return new EffectScope(detached);
}

effect.ts

export class ReactiveEffect {
  // ...
  constructor(public fn, private scheduler) {
    // 参数public fn => public fn; 参数 fn; this.fn = fn
    + recordEffectScope(this);
  }
  // ...
}