🎋 Ref响应式

67 阅读4分钟

原文地址: alvis.org.cn/posts/da023…

从 0 到 1 手撕 Reactive 响应式教程导航 🚥🚥🚥

  1. 🥬 建立一个响应式系统
  2. 🍒 自动收集依赖以及触发 effect
  3. 🎋 Ref响应式 effec ⇦ 当前位置 🪂

1. 对象访问器属性

将键与两个访问器函数(get 和 set)相关联,以获取或者存储值。

// 对象访问器
let user = {
    firstName: "John",
    lastName: "Doe",
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    },
    set fullName(value) {
        [this.firstName, this.lastName] = value.split(" ");
    }
}

console.log(user.fullName); // 输出 "John Doe"
user.fullName = "Alice White";
console.log(user.fullName); // 输出 "Alice White"

一个访问器属性,有着以下得特性:

  1. get: 该函数使用一个空的参数列表,以便有权对值执行访问时,获取属性值。参见 getter
  2. set: 使用包含分配值的参数调用的函数。每当尝试更改指定属性时执行。参见 setter
  3. enumerable: 一个布尔值,表示是否可以通过 for...in 循环来枚举属性。另请参阅枚举性和属性所有权,以了解枚举属性如何与其他函数和语法交互。
  4. configurable: 一个布尔值,表示该属性是否可以删除,是否可以更改为访问器属性,并可以更改其特性。

2. Ref

在之前得章节中,我们聊过了vue3的响应式核心tracktrigger如果还不了解的朋友,建议看一下前面的文章 {% post_link 建立一个响应式系统 🥬 建立一个响应式系统%} 。闲言少叙,我们直接开始。

简单回顾一下我们的核心内容:

  1. track

track的主要作用是帮助我们创建对应的映射关系,最后帮助我们添加依赖,weakMap的key存储对象地址,weakMap的value是一个MapMap的key存储的是对象的属性,Map的value存储的是一个Set集合,Set集合内部存储的就是我们该对象依赖的方法

// 响应式对象 -> Map
const targetMap = new WeakMap();

function track(target, key) {
  // 当前响应式对象是否建立映射关系
  let depsMap = targetMap(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);
}
  1. trigger

trigger的主要作用根据对象和key在存储的映射关系中,找到并运行相关的依赖。

function trigger(target, key) {  
  let depsMap = targetMap.get(target); // 检查当前响应式对象是否存在依赖,不存在则直接退出  
  if (!depsMap) return;  
  let dep = depsMap.get(key); // 该对象的属性存在依赖函数,循环运行effect  
  if (dep) {  
    dep.forEach((effect) => effect());  
  }  
}

2.1 Ref原理实现

Vue3 的做法是借助 ref将基础数据类型值进行一层封装,如果想读取数据的话,通过 refReturn.value,如果想设置新值的话,也通过 refReturn.value。接下来直接看伪代码,很简单。

function ref(raw) {
    let oldVal;
    const r =  {
        get value() {
            // 收集依赖
            track(r, 'value');
            return raw;
        },
        set value(newVal) {
            // salvePrice.value = newVal
            // 将新值newVal传递给raw
            oldVal = raw;
            raw = newVal;
            if(oldVal !== raw) {
                // 触发依赖
                trigger(r, 'value');
            }
        }
    }
    return r;
}

2.2 Ref源码解读

  1. 第一步简简单单调用createRef函数,见名知意,创建一个Ref
function ref(value) {
  return createRef(value, false);
}
  1. 第二步简简单单,先判断是不是ref,如果是直接返回,不是则new RefImpl
function createRef(rawValue, shallow) {
  // 查看rawValue是不是Ref,要是Ref直接返回
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}
  1. 第三步简简单单,检查对象是否带有__v_raw属性,最后返回普通对象
function toRaw(observed) {
  // 检查对象是否带有__v_raw
  const raw = observed && observed["__v_raw"];
  return raw ? toRaw(raw) : observed;
}
  1. 第四步简简单单,如果传递过来的是对象,直接使用reactive变成响应式对象
const toReactive = (value) => shared.isObject(value) ? reactive(value) : value;
  1. 第五步简简单单,判断是否是shallowRef浅层的ref
function isShallow(value) {
  // __v_isShallow是shallowRef的标识符
  return !!(value && value["__v_isShallow"]);
}

有人会问,为什么这些东西就是标识符的呢?你是怎么知道的呢?下面给大家扩展一下,我们就用isReadonly举例子:

image.png 从上图中我们可以看出来,value被断言成Target类型。

然后我们在继续看Target对象类型 image.png

这样就很明显了把,我们要从value对象中取到ReactiveFlages.IS_READONLY类型的属性对应的boolean值,而ReactiveFlages.IS_READONLY类型是一个枚举: image.png

所以__v_isReadonlyreadonly的标识符。如果想看具体简洁的可以去看isRef

  1. 判断是否是readonly只读的
function isReadonly(value) {
  // __v_isReadonly 是readonly的标识符
  return !!(value && value["__v_isReadonly"]);
}

上面的代码(3-6)其实都是一些辅助函数,不过不要担心,下面的主要内容也是简简单单,一看就会:

class RefImpl {
  constructor(value, __v_isShallow) {
	// false -> ref  true -> shallowRef
    this.__v_isShallow = __v_isShallow;
    this.dep = void 0;
    this.__v_isRef = true;
    // ref函数__v_isShallow===false, toRaw是将响应式对象变成普通对象,普通对象进入raw直接返回
    this._rawValue = __v_isShallow ? value : toRaw(value);
    // toReactive判断传进去的数据是否是对象,如果是对象交给reactive,反之直接返回
    this._value = __v_isShallow ? value : toReactive(value);
  }

  get value() {
    // 收集依赖
    trackRefValue(this);
    // 外界读取.value的时候,将this._value返回  obj.value取的就是this._value
    return this._value;
  }

  set value(newVal) {
	// shallowRef readobly 查看是不是其中之一 
    const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
    newVal = useDirectValue ? newVal : toRaw(newVal);
    // 比较新旧两个值是不是同一个值或者对象,不是同一个则进入if代码块内部
    if (shared.hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal;
      this._value = useDirectValue ? newVal : toReactive(newVal);
      // 执行依赖函数
      triggerRefValue(this, newVal);
    }
  }
}