Vue.js 3.0的Composition API中的三个创建响应式数据的函数reactive、toRefs、ref

三个创建响应式数据的函数reactive、toRefs、ref

  • reactive:把对象转换成一个响应式对象,也就是一个Proxy对象。

  • toRefs:它可以把一个代理对象中的所有属性也都转换成响应式的对象,toRefs()在处理这个对象的属性的时候,类似于ref

  • ref: 把基本类型的数据转换成响应式对象。


先从一个问题看起, 响应式对象解构过后就不再是响应式的对象了,因为我们在创建响应式对象的时候使用reactive函数将数据封装成了Proxy对象
例如,以下的代码是不可以正常工作的:
【案例代码在线演示地址】

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document 01 for Vue 3.0 Composition API</title>
  </head>

  <body>
    <div id="app">
      x: {{ x }} <br />
      y: {{ y }}
    </div>
    <script type="module">
      // 当前重构抽离的函数可以放到一个模块中,将来在任何组件中都可以使用,你可以尝试使用相同的方式实现其他的逻辑
      function useMousePosition() {
        const position = reactive({
          x: 0,
          y: 0,
        });

        const update = (e) => {
          position.x = e.pageX;
          position.y = e.pageY;
        };

        onMounted(() => {
          window.addEventListener('mousemove', update);
        });
        onUnmounted(() => {
          window.removeEventListener('mousemove', update);
        });
        return position;
      }
      import {
        createApp,
        reactive,
        onMounted,
        onUnmounted,
      } from './node_modules/vue/dist/vue.esm-browser.js';

      const app = createApp({
        setup() {
          // 第一个参数, props 接收外部传入的参数,是一个响应式的对象,它不能被解构。
          // 第二个参数, context ,是一个对象,具有三个成员 attrs、emit、slots
          // 需要返回一个对象,setup中返回的对象可以使用在模版,methods,computed,以及生命周期的钩子函数中
          // const position = useMousePosition();
          const { x, y } = useMousePosition();
          return {
            x,
            y,
          };
        },
      });

      console.log(app);

      app.mount('#app');
    </script>
  </body>
</html>

因为useMousePosition的返回值position原本是个响应式对象Proxy对象,将来访问其xy的时候会调用代理对象中的getter拦截收集依赖,当xy变化之后,会调用代理对象中的setter进行拦截触发更新。当我们把代理对象解构的时候,const {x, y} = useMousePosition(), 就相当于定义了 x 和 y 两个变量来接收 position.x 和 position.y ,而基本类型的赋值就相当于把值在内存中复制了一份,所以这里解构得到的就是两个基本类型的变量,跟Proxy代理对象无关,当重新给 x 和 y 赋值的时候,也不会弟调用代理对象的 setter,无法触发更新的操作。所以**是不能够对当前的响应式对象进行解构**。

babel对解构代码降级之后其实就是定义了 x 和 y 两个变量,用来接收position.xposition.y。如图示:
在这里插入图片描述


如果我们真要想像这样这么做的话,还是有办法的。

toRefs()

这是就需要用到一个新的API,叫 toRefs(),下面我们来演示一下如何使用toRefs()
【在线演示代码】

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document 01 for Vue 3.0 Composition API</title>
  </head>

  <body>
    <div id="app">
      x: {{ x }} <br />
      y: {{ y }}
    </div>
    <script type="module">
      import {
        createApp,
        reactive,
        onMounted,
        onUnmounted,
        toRefs,
      } from './node_modules/vue/dist/vue.esm-browser.js';
      // 当前重构抽离的函数可以放到一个模块中,将来在任何组件中都可以使用,你可以尝试使用相同的方式实现其他的逻辑
      function useMousePosition() {
        const position = reactive({
          x: 0,
          y: 0,
        });

        const update = (e) => {
          position.x = e.pageX;
          position.y = e.pageY;
        };

        onMounted(() => {
          window.addEventListener('mousemove', update);
        });
        onUnmounted(() => {
          window.removeEventListener('mousemove', update);
        });
        return toRefs(position);
      }

      const app = createApp({
        setup() {
          // 第一个参数, props 接收外部传入的参数,是一个响应式的对象,它不能被解构。
          // 第二个参数, context ,是一个对象,具有三个成员 attrs、emit、slots
          // 需要返回一个对象,setup中返回的对象可以使用在模版,methods,computed,以及生命周期的钩子函数中
          // const position = useMousePosition();
          const { x, y } = useMousePosition(); // 这里结构出来的x和y都是响应式对象,具有一个value,在模版使用中可以省略x.value;但是在代码中使用的时候value是不可省略的。
          return {
            x,
            y,
          };
        },
      });

      console.log(app);

      app.mount('#app');
    </script>
  </body>
</html>

在这里插入图片描述
我们只需要在useMousePosition()返回之前使用toRefs()函数将响应式的Proxy对象转换成引用即可
toRefs()函数的作用就是把响应式对象中的所有属性也转换成响应式的。
其原理是:

  • toRefs(proxyObj)要求我们传入的参数必须是一个Proxy对象,如果不是的话,它会抱警告提示需要传递的是代理Proxy对象。
  • 接下来它内部会创建一个新的对象,然后遍历传入的这个代理Proxy对象的所有属性,把所有属性的值都转换成代理对象。
  • 注意:toRefs()里面是把传入的proxyObj代理对象的所有属性的值都转换成响应式的对象,然后再挂载到新创建的对象上,最后把这个新创建的的对象返回。它内部会为代理对象的每一个属性创建一个具有value属性的对象,该对象是响应式的。value属性具有gettersetter。这一点和下面要讲的**ref()函数类似**。getter里面返回代理对象中对应属性的值;setter中给代理对象的属性赋值。所以我们返回的每一个属性都是响应式的

所以我们可以解构使用了toRefs()转换过后的响应式对象,结构的每一个属性也都是响应式的

ref()

  • ref()的作用是把普通数据转换成响应式数据,也就是把基本类型的数据包装成具有value值的响应式对象。
  • reactive()不同的是,reactive()是把一个对象转换成响应式数据

ref()的使用案例演示:【在线案例地址】

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document 01 for Vue 3.0 Composition API</title>
  </head>

  <body>
    <div id="app">
      <button @click="increase">count值➕1</button>
      <span>{{count}}</span>
    </div>
    <script type="module">
      import {
        createApp,
        ref,
      } from './node_modules/vue/dist/vue.esm-browser.js';

      function useCount() {
        const count = ref(0);
        return {
          count,
          increase: () => {
            count.value++;
          },
        };
      }
      const app = createApp({
        setup() {
          // 第一个参数, props 接收外部传入的参数,是一个响应式的对象,它不能被解构。
          // 第二个参数, context ,是一个对象,具有三个成员 attrs、emit、slots
          // 需要返回一个对象,setup中返回的对象可以使用在模版,methods,computed,以及生命周期的钩子函数中
          return { ...useCount() };
        },
      });
      app.mount('#app');
    </script>
  </body>
</html>

案例分析:我们知道基本数据类型存储的是值,所以它不可能是响应式数据,我们知道响应式数据要通过getter收集依赖,通过setter触发更新。那么:

  • ref()传入的参数如果是个对象的话,它内部会调用reactive返回一个响应式对象。
  • ref()传入的参数如果是基本类型的值,例如我们的案例中传入的0,那它内部会创建一个具有value属性的对象,该对象的value属性具有gettersettergetter里面收集依赖;setter中触发更新。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值