Vue3 之 reactive、ref、toRef、toRefs 使用与区别,源码分析详细注释

发布时间 2023-06-14 15:55:57作者: Echoyya、

reactive、ref、toRef、toRefs 使用与区别

reactive

  • 参数传入普通对象,不论层级多深都可以返回响应式对象,(参数只能是对象)
  • 但是解构、扩展运算符会失去响应式

ref 作用及用法

  • 参数可以为任意类型,推荐使用基本类型
  • 使用时 需要通过 xxx.value 的形式获取
  • 本质是拷贝粘贴一份数据,脱离了与源数据的交互
  • 将对象中属性变成响应式数据,修改该数据是不会影响到源数据,但是会更新视图
<template>
  <div id="contain">
    {{ refTest }}
    <button @click="change">click</button>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
const hello = "hello"; // 源数据
const refTest = ref(hello); // 相当于复制了一份

function change() {
  refTest.value = "world"; // 会更新视图
  console.log(refTest);
  console.log(hello); // 源数据不变 hello
}
</script>

toRef 作用及用法

  • 针对 reactive 解构失去响应式的问题,创建了 toRef,用于为源响应式对象上的属性新建一个 ref,保持对源对象属性的响应式交互。
  • 语法:toRef(target, key)
  • 使用时 需要通过 xxx.value 的形式获取
  • 本质是引用,与源数据有交互,修改该数据是会影响源数据,但是不会更新视图,如果需要更新视图,需要使用 reactive 包裹源数据
<template>
  <div id="contain">
    <p>{{ toRefTest }} <button @click="changeNor">click</button></p>
    <p>{{ toRefReactTest }} <button @click="changeRea">click</button></p>
  </div>
</template>

<script lang="ts" setup>
import { reactive, toRef } from "vue";

// 普通对象
const state = { name: "hello" }; // 源数据
const toRefTest = toRef(state, "name"); // toRef 本质是引用
function changeNor() {
  toRefTest.value = "world"; // 不会更新视图
  console.log(state); // 会影响源数据
}

// ------------------------------------------
// reactive 包裹源数据
const reactState = reactive({ name: "hello" }); // reactive 包裹源数据
const toRefReactTest = toRef(reactState, "name");
function changeRea() {
  toRefReactTest.value = "world"; // 会更新视图
  console.log(reactState); // 会影响源数据
}
</script>

toRefs 作用及用法

  • 就是批量的 toRef 操作,toRefs 是一次性将 reactive 中的所有属性都转为 ref
  • 语法:toRefs(target)
  • 同样可以用于解构 reactive 的响应式对象
  • 使用时 需要通过 xxx.value 的形式获取
  • 本质是引用,与源数据有交互,修改该数据是会影响源数据,但是不会更新视图,如果需要更新视图,需要使用 reactive 包裹源数据
<template>
  <div id="contain">
    <p>
      普通对象:{{ name }} -- {{ age }}
      <button @click="changeNor">click</button>
    </p>
    <p>
      reactive对象:{{ rename }} -- {{ reage
      }}<button @click="changeRea">click</button>
    </p>
  </div>
</template>

<script lang="ts" setup>
import { reactive, toRefs } from "vue";
// 普通对象
const state = { name: "hello", age: 18 }; // 源数据
const { name, age } = toRefs(state); //  本质是引用
function changeNor() {
  name.value = "world"; // 不会更新视图
  age.value++;
  console.log(state); // 会影响源数据
}
// ------------------------------------------
// reactive 包裹源数据
const reactState = reactive({ rename: "hello", reage: 18 }); // reactive 包裹源数据
const { rename, reage } = toRefs(reactState); //  本质是引用
function changeRea() {
  rename.value = "world"; // 会更新视图
  reage.value++;
  console.log(reactState); // 会影响源数据
}
</script>

ref,toRef,toRefs 源码实现解析详细注释

ref 和 reactive 的底层原理区别: reactive 内部采用 proxy ,而 ref 中内部采用的是 defineProperty

ref、shallowRef 源码实现。使用class RefImpl实现,会被 babel 编译成defineProperty
其中 share.ts 和 reactive.ts 文件中的方法,不在赘述,详见上一篇 响应式原理的文章

import { hasChanged, isArray, isObject } from "./shared";
import { TrackOpTypes, TriggerOrTypes } from "./shared";
import { track, trigger } from "./effect";
import { reactive } from "./reactive";

export function ref(value) {
  return createRef(value);
}

//  shallowRef 只能处理基本类型数据
export function shallowRef(value) {
  return createRef(value, true);
}

const convert = (val) => (isObject(val) ? reactive(val) : val);
class RefImpl {
  public _value;
  public __v_isRef = true; // 实例添加 __v_isRef, 表示是一个ref属性
  constructor(public rawValue, public shallow) {
    // 1.参数前面加修饰符 表示实例属性
    this._value = shallow ? rawValue : convert(rawValue); // 如果是深度监听 需要把里面的都变成响应式的
  }
  // 2. 类的属性访问器
  get value() {
    // 3. 依赖收集 代理取值取_value
    track(this, TrackOpTypes.GET, "value"); // 依赖收集
    return this._value;
  }
  set value(newValue) {
    // 4. 判断老值和新值是否有变化
    if (hasChanged(newValue, this.rawValue)) {
      this.rawValue = newValue; // 新值会作为老值
      this._value = this.shallow ? newValue : convert(newValue);
      // 5. 触发更新
      trigger(this, TriggerOrTypes.SET, "value", newValue);
    }
  }
}
function createRef(rawValue, shallow = false) {
  return new RefImpl(rawValue, shallow);
}

toRef,toRefs 源码实现。使用class ObjectRefImpl实现,会被 babel 编译成defineProperty

class ObjectRefImpl {
  public __v_isRef = true; // 实例添加 __v_isRef, 表示是一个ref属性
  constructor(public target, public key) {}
  get value() {
    return this.target[this.key]; // 如果原对象是响应式的就会依赖收集
  }
  set value(newValue) {
    this.target[this.key] = newValue; // 如果原来对象是响应式的 那么就会触发更新
  }
}

// 将对象的某一个值转化成 ref类型
export function toRef(target, key) {
  return new ObjectRefImpl(target, key);
}

//  可能传递的是一个数组 或者对象,属性全部转化成 ref类型
export function toRefs(object) {
  const ret = isArray(object) ? new Array(object.length) : {};
  for (let key in object) {
    ret[key] = toRef(object, key); // 遍历调用toRef方法
  }
  return ret;
}