Vue 3 Composition API:Ref或Reactive





现在,当我写这篇文章时,我们离Vue 3的发布越来越近。我认为,最有趣的事情是观察其他开发人员如何看待和使用它。在过去的几个月中,我有机会试用Vue 3,但我知道有些人还没有。



新版本中最重要的创新是Composition API。它提供了一种创建组件的替代方法,与现有的Options API有很大不同。我不难承认,当我第一次见到他时,我并不理解他。但是,随着它的应用,含义开始出现。您可能不会使用Composition API重写整个应用程序,但是本文将为您提供一个思考组件创建和组合如何工作的机会。







我最近做了几个演示,一个常见的问题是何时使用Ref和何时使用反应性来声明反应性。我没有一个好的答案,花了几周时间才找到答案,这篇文章是我研究的结果。



此外,我想指出的是,以上是我的观点,请不要认为“仅通过这种方式,而不必这样做”是必要的。我计划以这种方式使用Ref和Reactive,直到有人提出其他建议,或者直到我自己找出更好的方法为止。我认为了解任何新技术都需要花费一些时间,然后可能会出现经过验证的技术。



在继续之前,我假设您至少已经简要地熟悉了成分API并了解其组成。本文着重于ref和react之间的差异,而不是API组成机制。



Vue 2反应性



为了让您快速上手,我将快速介绍如何在Vue 2应用程序中创建反应式属性。当您希望Vue 2跟踪属性的更改时,需要在data函数返回的对象中声明它们。



<template>
  <h1>{{ title }}</h1>
</template>

<script>
  export default {
    data() {
      return {
        title: "Hello, Vue!"
      };
    }
  };
</script>


在Vue 2的幕后,将为每个属性调用Object.defineProperty()来创建一个获取器和设置器以跟踪更改。这是对该过程的最简单的解释,我想传达一个想法:其中没有魔术。您无法在任何地方创建反应性属性,并且期望Vue跟踪它们的变化。您需要在数据函数中设置反应特性。



REF和ACTIVE



在使用Options API时,在声明反应性时我们需要遵循一些规则,在使用Composition API时也应遵循同样的规则。您不能只是创建一个属性并期望其反应性。在下面的示例中,我声明了title属性,setup()函数将其返回,从而使其可用于模板。



<template>
  <h1>{{ title }}</h1>
</template>

<script>
  export default {
    setup() {
      let title = "Hello, Vue 3!";
      return { title };
    }
  };
</script>




这将起作用,但title属性将不会起作用。那些。如果有人更改标题,这些更改将不会反映在众议院中。例如,如果您在5秒钟后更改标题,则下面的代码将不会更改首页。



<template>
  <h1>{{ title }}</h1>
</template>

<script>
  export default {
    setup() {
      let title = "Hello, Vue 3!";

      setTimeout(() => {
        title = "THIS IS A NEW TITLE";
      }, 5000);

      return { title };
    }
  };
</script>




我们可以导入ref并将其用于使该属性具有反应性。在后台,Vue 3将创建一个Proxy



<template>
  <h1>{{ title }}</h1>
</template>

<script>
  import { ref } from "vue";

  export default {
    setup() {
      const title = ref("Hello, Vue 3!");

      setTimeout(() => {
        //   ,   .value ...
        //   
        title.value = "New Title";
      }, 5000);

      return { title };
    }
  };
</script>




我想澄清一下,关于Ref和Reactive,我认为有两种不同的情况。首先是像上面的示例一样创建一个组件,您需要使用反应特性。第二个是创建允许在组件和功能中使用合成的功能时。我们将在本文中讨论这两种情况。



参考



创建简单类型的属性时,ref()是您的首选。这不是灵丹妙药,但值得一开始。让我提醒您JavaScript的七个原始类型:



  • 大整数
  • 布尔型
  • 符号
  • 空值
  • 未定义




import { ref } from "vue";

export default {
  setup() {
    const title = ref("");
    const one = ref(1);
    const isValid = ref(true);
    const foo = ref(null);
  }
};




在前面的示例中,标题为String类型,因此为了使该属性具有反应性,我们选择ref()。如果下面的代码使您不担心某些问题,那么我也有同样的问题。



import { ref } from "vue";

export default {
  setup() {
    const title = ref("Hello, Vue 3!");

    setTimeout(() => {
      title.value = "New Title";
    }, 5000);

    return { title };
  }
};




如果标题会更改,为什么还要使用const?为什么不使用let?如果将标题打印到控制台,则可能希望看到Hello,Vue 3!,但是它将显示一个对象:



{_isRef: true}
value: (...)
_isRef: true
get value: ƒ value()
set value: ƒ value(newVal)
__proto__: Object




ref()函数将接受一个参数,并返回一个反应性和可变的ref对象。Ref对象具有一个属性,值,它引用参数。这意味着,如果要获取或更改值,则必须使用title.value,并且由于这是一个不会更改的对象,因此我将其声明为const。



致电REF



下一个问题是为什么我们不在模板中调用title.value?



<template>
  <h1>{{ title }}</h1>
</template>




当Ref作为呈现上下文中的属性(由setup()函数返回的对象)作为属性返回并在模板中被引用时,Ref将自动返回值。您无需在模板中添加.value。



在setup()函数内部,计算属性的工作方式相同,将它们称为.value。




反应性的



我们刚刚看到了如何使用ref()使简单类型的属性具有反应性。如果我们想创建一个反应性对象怎么办?我们仍然可以使用ref(),但是在Vue的幕后,我将使用react(),因此我将坚持使用react()。



另一方面,react()不适用于基本类型。react()函数接受一个对象并返回原始对象的反应代理。这等效于Vue 2中的.observable(),并且此函数名称已更改,以避免与RxJS中的observable混淆。



import { reactive } from "vue";

export default {
  setup() {
    const data = reactive({
      title: "Hello, Vue 3"
    });

    return { data };
  }
};




主要区别在于我们如何引用模板中的反应对象。在前面的示例中,数据是一个包含标题属性的对象。您需要像这样在模板中引用它-data.title:



<template>
  <h1>{{ data.title }}</h1>
</template>

<script>
  import { ref } from "vue";

  export default {
    setup() {
      const data = ref({
        title: "Hello, Vue 3"
      });

      return { data };
    }
  };
</script>




组件中REF和REACTIVE之间的区别



根据我们所讨论的内容,答案似乎在暗示自己?我们将ref()用于简单类型,将react()用于对象。当我开始构建组件时,事实证明并非总是如此,文档中说:



在某种程度上,使用ref和react之间的区别可以与您用JavaScript编写标准程序逻辑的方式相媲美。




我仔细考虑了这个短语,得出以下结论。随着应用程序的增长,我获得了以下属性:



export default {
  setup() {
    const title = ref("Hello, World!");
    const description = ref("");
    const content = ref("Hello world");
    const wordCount = computed(() => content.value.length);

    return { title, description, content, wordCount };
  }
};




在JavaScript中,我将理解这些都是页面的所有属性。在这种情况下,我会将它们分组为一个页面对象,为什么现在不这样做。



<template>
  <div class="page">
    <h1>{{ page.title }}</h1>
    <p>{{ page.wordCount }}</p>
  </div>
</template>

<script>
  import { ref, computed, reactive } from "vue";

  export default {
    setup() {
      const page = reactive({
        title: "Hello, World!",
        description: "",
        content: "Hello world",
        wordCount: computed(() => page.content.length)
      });

      return { page };
    }
  };
</script>




这是我对Ref或Reactive的处理方式,但是很高兴知道您的意见。你也一样吗 也许这不是正确的方法?请评论。



合成逻辑



在组件中使用ref()或react()时,您不会出错。这两个函数都将创建反应性数据,并且如果您了解如何在setup()函数和模板中访问它,则没有问题。



当您开始编写合成函数时,您需要了解它们之间的区别。我使用了RFC文档中的示例,其中一些示例很好地说明了细微差别。



您需要创建用于跟踪鼠标位置的逻辑。必要时,您还应该能够在任何组件中使用相同的逻辑。您创建一个合成函数,该函数跟踪x和y坐标,然后将其返回给客户端代码。



import { ref, onMounted, onUnmounted } from "vue";

export function useMousePosition() {
  const x = ref(0);
  const y = ref(0);

  function update(e) {
    x.value = e.pageX;
    y.value = e.pageY;
  }

  onMounted(() => {
    window.addEventListener("mousemove", update);
  });

  onUnmounted(() => {
    window.removeEventListener("mousemove", update);
  });

  return { x, y };
}




如果要在组件中使用此逻辑并调用此函数,请对返回的对象进行解构,然后将x和y坐标返回到模板。



<template>
  <h1>Use Mouse Demo</h1>
  <p>x: {{ x }} | y: {{ y }}</p>
</template>

<script>
  import { useMousePosition } from "./use/useMousePosition";

  export default {
    setup() {
      const { x, y } = useMousePosition();
      return { x, y };
    }
  };
</script>




这将起作用,但是如果在查看该函数之后,您决定重构并返回一个位置对象而不是x和y:



import { ref, onMounted, onUnmounted } from "vue";

export function useMousePosition() {
  const pos = {
    x: 0,
    y: 0
  };

  function update(e) {
    pos.x = e.pageX;
    pos.y = e.pageY;
  }

  // ...
}




这种方法的问题在于,组合功能的客户端必须始终具有对返回对象的引用,以使反应性得以持久。这意味着我们不能破坏对象或应用散布运算符:



//  
export default {
  setup() {
    //  !
    const { x, y } = useMousePosition();
    return {
      x,
      y
    };

    //  !
    return {
      ...useMousePosition()
    };

    //     
    //   `pos`  ,      x  y : `pos.x`  `pos.y`
    return {
      pos: useMousePosition()
    };
  }
};




但这并不意味着在这种情况下我们不能使用反应式。有一个toRefs()函数,可将反应对象转换为简单对象,其每个属性都是原始对象对应属性的ref。



function useMousePosition() {
  const pos = reactive({
    x: 0,
    y: 0
  });

  // ...
  return toRefs(pos);
}

// x & y  ref!
const { x, y } = useMousePosition();




因此,创建合成函数时需要考虑一些方面。如果您了解客户端代码如何使用您的函数,那么一切都会好起来的。





当我第一次开始使用Composition API时,我对何时应用ref()和何时应用react()的问题感到困惑。也许我还是做得不好,除非有人告诉我,否则我将坚持这种方法。希望有助于澄清一些问题,并希望听到您的反馈。



快乐编码



All Articles