Vue3源码学习之路-实现ref、toRef、toRefs

ref说明

ref官方文档

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。
ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。

如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。

若要避免这种深层次的转换,请使用 shallowRef() 来替代。

与reactive的区别

  • reactive可以实现对象的响应式代理
  • ref可以把基本数据类型也变成响应式数据

ref的使用示例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./reactivity.global.js"></script>
    <!-- <script src="../../../node_modules/vue/dist/vue.global.js"></script> -->
    <script>
      const app = document.getElementById('app');
      const { ref, effect } = VueReactivity;

      const status = ref(false);

      effect(() => {
        app.innerHTML = status.value ? '1' : '0';
      });

      setTimeout(() => {
        status.value = !status.value;
      }, 1000);
    </script>
  </body>
</html>

实现ref

packages/reactivity/src/ref.ts

import { isObject } from '@vue/shared';
import { trackEffects, triggerEffects } from './effect';
import { reactive } from './reactive';

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

class RefImpl {
  public _value;
  public dep = new Set();
  public readonly __v_isRef = true;

  constructor(public rawValue) {
    this._value = toReactive(rawValue);
  }

  get value() {
    // 依赖收集
    trackEffects(this.dep);

    return this._value;
  }

  set value(newValue) {
    if (newValue !== this.rawValue) {
      this._value = toReactive(newValue);
      this.rawValue = newValue;
      triggerEffects(this.dep);
    }
  }
}

export function ref(value) {
  return new RefImpl(value);
}

简单的ref就实现了

toRefs说明

toRefs官方文档
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

当从组合式函数中返回响应式对象时,toRefs 相当有用。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性。

toRefs 在调用时只会为源对象上可以枚举的属性创建 ref。如果要为可能还不存在的属性创建 ref,请改用 toRef。

toRef、toRefs的使用示例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./reactivity.global.js"></script>
    <!-- <script src="../../../node_modules/vue/dist/vue.global.js"></script> -->
    <script>
      const app = document.getElementById('app');
      const { effect, reactive, toRefs, toRef } = VueReactivity;

      const state = reactive({
        foo: 1,
        bar: 'bar',
        des: 'des text',
      });

      const stateAsRefs = toRefs(state);

      const stateAsRef = toRef(state, 'des');

      const { foo, bar } = stateAsRefs;

      const des = stateAsRef;

      effect(() => {
        app.innerHTML = foo.value + '__' + bar.value + '__' + des.value;
      });

      // 这个 ref 和源属性已经“链接上了”
      setTimeout(() => {
        state.foo++;
      }, 1000);

      setTimeout(() => {
        bar.value = 'bowen';
        des.value = 'none';
      }, 2000);
    </script>
  </body>
</html>

实现toRef、toRefs

packages/reactivity/src/ref.ts 新增

// 将访问或者设置.value的操作代理到源对象
class ObjectRefIMpl {
  constructor(public object, public key) {}

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

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

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

export function toRefs(object) {
  const result = isArray(object) ? new Array(object.length) : {};

  for (const key in object) {
    result[key] = toRef(object, key);
  }

  return result;
}

使用toRefs处理完数据之后,我们取值都需要加上.value,书写也是不方便的,所以有proxyRefs这样一个方法可以去除.value直接取值和赋值

proxyRefs的使用示例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./reactivity.global.js"></script>
    <!-- <script src="../../../node_modules/vue/dist/vue.global.js"></script> -->
    <script>
      const app = document.getElementById('app');
      const { effect, ref, proxyRefs } = Vue;

      const name = ref('bowen');
      const age = ref(18);

      const person = proxyRefs({ name, age })

      effect(() => {
        app.innerHTML = person.name + '__' + person.age;
      });
    </script>
  </body>
</html>

实现proxyRefs

packages/reactivity/src/ref.ts 新增

export function proxyRefs(object) {
  return new Proxy(object, {
    get(target, key, receiver) {
      let res = Reflect.get(target, key, receiver);
      return res.__v_isRef ? res.value : res;
    },
    set(target, key, value, receiver) {
      let oldValue = target[key];
      if (oldValue.__v_isRef) {
        oldValue.value = value;
        return true;
      }
      return Reflect.set(target, key, value, receiver);
    },
  });
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值