9-实现 ref

126 阅读2分钟

思考功能

  1. ref的数据是双向绑定的
  2. ref的数据通过.value取

根据功能写单侧

新建 ref.spec.ts

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

describe("ref", () => {
  /** 看得见的思考
   * 1. ref对象的值包在value
   * 2. ref的数据也要收集,触发侦听,做双向绑定
   * 3. 做缓存,重新赋值时判断值是否相等
   */
  it("should be reactive", () => {
    const a = ref(1);
    let dummy;
    let calls = 0;
    effect(() => {
      calls++;
      dummy = a.value;
    });
    expect(calls).toBe(1);
    expect(dummy).toBe(1);
    /** 思考
     * 1. 依赖ref的数据发生变动,对应也要变动
     * 2. ref要触发effect,才能侦听到数据变化
     */
    a.value = 2;
    expect(calls).toBe(2);
    expect(dummy).toBe(2);
    // same value should not trigger
    /** 思考
     * 1. ref做缓存,要加个标识
     */
    a.value = 2;
    expect(calls).toBe(2);
    expect(dummy).toBe(2);
  });

  /** 看得见的思考
   * 1. ref支持基本类型和引用类型
   * 2. ref可以深度侦听数据变化
   */
  it("should make nested properties reactive", () => {
    const a = ref({
      count: 1,
    });
    let dummy;
    effect(() => {
      dummy = a.value.count;
    });
    expect(dummy).toBe(1);
    a.value.count = 2;
    expect(dummy).toBe(2);
  });

核心逻辑

实现ref.value

/*
 * @Author: Lin zefan
 * @Date: 2022-03-17 18:23:36
 * @LastEditTime: 2022-03-20 15:11:23
 * @LastEditors: Lin zefan
 * @Description: ref
 * @FilePath: \mini-vue3\src\reactivity\ref.ts
 *
 */
export function ref(ref) {
  return new RefImpl(ref);
}

class RefImpl {
  private _value: any;
  constructor(value) {
    this._value = value;
  }

  get value() {
    return this._value;
  }

  set value(newVal) {
    this._value = newVal;
  }
}

实现ref缓存

shared/index.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-15 19:28:09
 * @LastEditTime: 2022-03-17 19:03:20
 * @LastEditors: Lin zefan
 * @Description: 公用hook
 * @FilePath: \mini-vue3\src\shared\index.ts
 *
 */
export function hasChanged(val, newVal) {
  return Object.is(val, newVal);
}

reactivity/ref.ts

import { hasChanged } from "../shared";
/*
 * @Author: Lin zefan
 * @Date: 2022-03-17 18:23:36
 * @LastEditTime: 2022-03-20 15:11:23
 * @LastEditors: Lin zefan
 * @Description: ref
 * @FilePath: \mini-vue3\src\reactivity\ref.ts
 *
 */
export function ref(ref) {
  return new RefImpl(ref);
}

class RefImpl {
  private _value: any;
  constructor(value) {
    this._value = value;
  }

  get value() {
    return this._value;
  }

  set value(newVal) {
+   /** 思考
+     * 1. 先判断新老值,值不相等再做更新
+     */
+    if (hasChanged(this._value, newVal)) return;
    this._value = newVal;
  }
}

实现ref动态侦听(完整代码)

import { reactive } from ".";
import { isObject, hasChanged } from "../shared";
import { isTracking, trackEffect, triggerEffect } from "./effect";

class RefImpl {
  _dep: any;
  private _value: any;
  __v_isRef = true;
  constructor(value) {
    /**
     * 1. 需要判断value是基本类型还是引用类型
     * 2. 引用类型需要用reactive包裹,做到深度侦听
     */
    this._value = convert(value);
    this._dep = new Set();
  }

  get value() {
    /** 思考
     * 1. get要收集依赖
     */
    isTracking() && trackEffect(this._dep);
    return this._value;
  }

  set value(newVal) {
    /** 思考
     * 1. 先判断新老值,值不相等再做更新
     * 2. 更新ref.value
     * 3. 更新依赖的值
     */
    if (hasChanged(this._value, newVal)) return;
    this._value = convert(newVal);
    triggerEffect(this._dep);
  }
}

function convert(value) {
  return isObject(value) ? reactive(value) : value;
}