我的回答

将接口的可重复部分及其功能提取到可重用的代码段中, 使我们的应用程序在可维护性和灵活性方面走得更远
组合式 API 可以将用户的逻辑关注点相关的代码配置在一起

参考回答

首先要了解以下 Composition API 设计的好处在哪里? 逻辑组合和复用、类型推导、打包尺寸等

在 vue3.0 之前所有的组件都可以看作一个可选项的配置集合,通过 data、computed、methods、watch、以及 created、mounted 等声明周期函数,用这个可选项集合来声明一个组件。
这样写的好处是组织结构清晰,但是在逻辑复用上就不太友好啦,我们都知道的是 js 中最简洁清晰的复用方式就是将逻辑封装到一个函数中,然后函数与函数之间相互调用。

Vue3.0 很好的支持 TS,而 TS 的最重要的一个特性就是类型推导,而函数相对于嵌套的对象来说对类型推导更加友好
另外,以函数形式组织的模块以具名方式导入使用,在tree-sharking的时候支持会更好

setup 函数

setup()函数式 vue3 中专门为组件提供的一个新属性,它是我们在使用 vue3 的 composition-api 的统一入口。也就是说我们使用的新特性都要在这个函数中进行。
执行时机:setup 函数会在beforeCreate()之后,created()之前执行。
参数: props、context
使用: 第一步和之前的写法一样,需要在 props 中定义外界传入的参数类型等

  1. props: {
  2. msg: String;
  3. }

setup函数的第一个形参就是用来接收props的数据的

  1. setup(props) {
  2. console.log(props);
  3. }

setup函数的第二个形参是一个上下文对象(context), 它包含了以下在vue2中组件实例的一些属性(需要通过this来调用的),包括如下:

  1. setup(props, context) {
  2. // 需要注意的是:在setup函数中,无法访问this
  3. console.log(props);
  4. console.log(context);
  5. console.log(context.attrs);
  6. console.log(context.slots);
  7. console.log(context.parent);
  8. console.log(context.emit);
  9. }

响应数据声明


在vue2中创建响应式数据的方式为:data、props;其中data中的数据是双向的,而props中的数据是单向的。在新特性中有两种方式能创建响应式数据:reactive()和ref()

reactive

reactive()函数接收一个普通对象,返回一个响应式的数据对象。等价于vue2中的Vue.observable()函数。

  1. <template>
  2. <div>
  3. <div>{{count}}</div>
  4. <button @click="count+=1">+1</button>
  5. </div>
  6. </template>
  7. <script>
  8. import { reactive } from "vue";
  9. export default {
  10. setup(props, context) {
  11. // 需要注意的是:页面所需要的数据必须在函数最后return出来,才能在页面模板中使用。
  12. const state = reactive({ count: 0 });
  13. return state;
  14. }
  15. };
  16. </script>

ref

ref() 函数将一个给定的值转化为响应式的数据对象,它返回一个对象,响应式的数据值需要通过调用value属性访问,而在页面模板中则可直接访问。

  1. <template>
  2. <div>
  3. <div>{{count}}</div>
  4. <button @click="count+=1">+1</button>
  5. </div>
  6. </template>
  7. <script>
  8. import { ref } from "vue";
  9. export default {
  10. name: "ref",
  11. setup() {
  12. const count = ref("1");
  13. console.log(count.value);
  14. count.value++;
  15. console.log(count.value);
  16. return {
  17. count
  18. };
  19. }
  20. };
  21. </script>

isRef()

isRef()用来判断某个值是否为ref()函数创建出来的对象

toRefs()

toRefs()函数可以将reactive创建出来的响应式对象转化为通过ref()创建出来的响应式对象

  1. <template>
  2. <div ref="app">
  3. <div>{{count}}</div>
  4. <button @click="add">+1</button>
  5. </div>
  6. </template>
  7. <script>
  8. import { reactive, toRefs } from "vue";
  9. export default {
  10. name: "toRefs",
  11. setup() {
  12. const state = reactive({
  13. count: 0
  14. });
  15. const add = () => [state.count++];
  16. return {
  17. ...toRefs(state),
  18. add
  19. };
  20. }
  21. };
  22. </script>

computed


computed()用来创建计算属性, 函数返回的是一个ref的实例。
创建只读的计算属性: 在调用computed()函数期间,传入一个函数,可以得到一个只读的计算属性。
创建可读可写的计算属性: 在调用computed()函数期间,传入一个包含get和set的对象,可以得到一个可读可写的计算属性。

  1. <template>
  2. <div class="computed">
  3. {{computedCountReadOnly}}
  4. <button @click="refCountReadOnly+=1">+1</button>
  5. <div>{{computedCountReadWrite}}</div>
  6. </div>
  7. </template>
  8. <script>
  9. import { ref, computed } from "vue";
  10. export default {
  11. setup() {
  12. const refCountReadOnly = ref(0);
  13. // 只读的计算属性
  14. const computedCountReadOnly = computed(
  15. () => refCountReadOnly.value + 1
  16. );
  17. // computedCountReadOnly.value = 9; // 这行代码会报错
  18. // 可读可写的计算属性
  19. const refCountReadWrite = ref(0);
  20. const computedCountReadWrite = computed({
  21. set: val => {
  22. refCountReadWrite.value = val - 1;
  23. },
  24. get: () => refCountReadWrite.value + 1
  25. });
  26. computedCountReadWrite.value = 11;
  27. return {
  28. refCountReadOnly,
  29. computedCountReadOnly,
  30. refCountReadWrite,
  31. computedCountReadWrite
  32. };
  33. }
  34. };
  35. </script>

watch

watch()函数用来监听某些数据的变化,从而触发某些特定的操作
基本用法:

  1. const refCount = ref(0);
  2. watch(() => console.warn(refCount.value));
  3. setInterval(() => {
  4. refCount.value++;
  5. }, 1000);

监视多个数据源:

  1. // reactive
  2. const state = reactive({ count: 0, name: "jokul" });
  3. watch(
  4. [() => state.count, () => state.name],
  5. ([newCount, newName], [oldCount, oldName]) => {
  6. console.log(`reactive $count 旧值:${oldCount}; 新值:${newCount}`);
  7. console.log(`reactive $name 旧值:${oldName}; 新值:${newName}`);
  8. },
  9. {
  10. lazy: true
  11. }
  12. );
  13. setTimeout(() => {
  14. state.count = 27;
  15. state.name = "Jokul";
  16. }, 2000);
  17. // ref
  18. let refCount = ref(0);
  19. let refName = ref("guohh");
  20. watch(
  21. [refCount, refName],
  22. ([newCount, newName], [oldCount, oldName]) => {
  23. console.log(`ref $count 旧值:${oldCount}; 新值:${newCount}`);
  24. console.log(`ref $name 旧值:${oldName}; 新值:${newName}`);
  25. },
  26. {
  27. lazy: true
  28. }
  29. );
  30. setTimeout(() => {
  31. refCount.value = 27;
  32. refName.value = "Jokul";
  33. }, 2000);

清除监听: 在setup()函数中创建的watch()监听,会在当前组件被销毁的时候自动清除,如果想要明确地或者提前结束某个监听,可以调用watch()的返回值

  1. // 定义变量接受watch函数的返回值 返回值为function
  2. const removeWtach = watch(()=>{})
  3. // 调用返回值函数,清除监听
  4. removeWtach()

在watch中清除无效的异步任务: 有时候当被watch函数监视的值发生变化时,或者watch本身被stop之后,我们期望能够清除哪些无效的异步任务,此时,watch回调函数提供了一个清除函数来执行清除工作。调用场景:

  • watch被重复执行
  • watch被强制stop

    1. <template>
    2. <div>
    3. <input type="text" v-model="keyword" />
    4. </div>
    5. </template>
    6. <script>
    7. import { ref, watch } from "vue";
    8. setup(){
    9. const keyword = ref("");
    10. const asyncprint = val => {
    11. return setTimeout(() => {
    12. console.log(val);
    13. }, 1000);
    14. };
    15. watch(
    16. keyword,
    17. (newVal, oldVal, clean) => {
    18. const timer = asyncprint(newVal);
    19. clean(() => clearTimeout(timer));
    20. },
    21. { lazy: true }
    22. );
    23. return { keyword };
    24. }
    25. </script>

    生命周期

    新的生命周期函数需要按需导入,并且在setup函数内使用。

vue2.x和vue3.x的关系:

  • beforeCreate() —- setup()
  • created() —- setup()
  • beforeMount() —- onBeforeMount()
  • mounted() —- onMounted()
  • beforeUpdate() —- onBeforeUpdate()
  • update() —- onUpdate()
  • beforeDestory() —- onBeforeUnmount()
  • destoryed() —- onUnmounted()
  • errorCaptured() —- onErrorCaptured()
    1. setup(){
    2. onBeforeMount(()=>{
    3. console.log("onBeforeMount")
    4. })
    5. onMounted(()=>{
    6. console.log("onMounted")
    7. })
    8. onBeforeUnmount(()=>{
    9. console.log("onBeforeUnmount")
    10. })
    11. // ...
    12. }

    provide & inject

    在vue2.x的时候,我们可以使用provide和inject实现嵌套组件之间的数据传递。但是在vue3.x中需要在setup()函数内使用。 ```javascript // 父组件 setup() { const father = ref(“父组件传递的”); provide(“father”, father); }

// 子组件 setup() { const data = inject(“father”); return { data }; }

  1. <a name="FA68J"></a>
  2. ### 获取页面DOM或者组件
  3. ```javascript
  4. <template>
  5. <div ref="divRef">页面dom</div>
  6. </template>
  7. <script>
  8. import { ref, onMounted } from "vue";
  9. export default {
  10. setup() {
  11. const divRef = ref(null);
  12. onMounted(() => {
  13. divRef.value.style.color = "blue";
  14. });
  15. return {
  16. divRef
  17. };
  18. }
  19. };
  20. </script>

defineComponent

这个函数仅仅提供了类型判断,主要是为了更好的结合TS来使用,能为setup()函数中的props提供完整的类型推断

  1. <script>
  2. import { defineComponent } from "@vue/composition-api";
  3. export default defineComponent({
  4. props: {
  5. foo: String
  6. },
  7. setup(props) {
  8. console.warn(typeof props.foo);
  9. }
  10. });
  11. </script>

当传递一个数字的foo时,页面就会报错:

  1. [Vue warn]: Invalid prop: type check failed for prop "foo". Expected String with value "0", got Number with value 0.