Vue3 Compitition Api

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

在 vue3.0 之前所有的组件都可以看做一个可选项的配置集合,通过 data、computed、methods、watch 以及 created、mounted 等生命周期函数,用这个可选项集合来声明一个组件。

这样写的好处是组织结构清晰,但是在逻辑复用上就不太友好了。我们都知道,js 中最简洁清晰的复用方式就是将逻辑封装到一个函数中,然后函数与函数之间相互调用。

Vue3.0 很好的支持了 Typescript,而 Typescript 的最重要的一个特性就是类型推导,而函数相对于嵌套的对象来说对类型推导更加的友好。

另外,以函数形式组织的模块以具名方式导入使用,在tree-sharking的时候支持会更好。

1.1 step 函数

setup()函数式vue3中专门为组件提供的一个新属性,它是我们在使用vue3的composition-api的统一入口。也就是说我们使用的新特性都要在这个函数中进行。

执行时机:

setup函数会在beforeCreate()之后,created()之前执行。

参数:

  • props
  • context

使用:

  • 第一步和之前的写法一样,需要在props中定义外界传入的参数类型等
props: {
    msg: String
}

  • setup函数的第一个形参就是用来接受props数据的。
setup(props) {
  console.log(props);
}

  • context
    setup函数的第二个形参是一个上下文对象,它包含了一下在vue2中组件实例的一些属性(需要通过this来调用的),包括如下:
setup(props, context) {
  console.log(props);
  console.log(context);
  console.log(context.attrs);
  console.log(context.slots);
  console.log(context.parent);
  console.log(context.root);
  console.log(context.emit);
  console.log(context.refs);
}

注意:在setup函数中,无法访问this。

1.2 响应数据声明

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

reactive:

reactive()函数接收一个普通对象,返回一个响应式的数据对象。 等价于vue2中的Vue.observable()函数。下面实现一个数量加一的操作。

<template>
    <div>
        <div>{{count}}</div>
        <button @click="count+=1">+1</button>
    </div>
</template>
<script>
import { reactive } from "vue";
export default {
    setup(props, context) {
        const state = reactive({ count: 0 });
        return state;
    }
};
</script>

需要注意的是页面需要的数据必须在函数最后retrun出来,才能在页面模板中使用。

ref:

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

<template>
    <div>
        <div>{{count}}</div>
        <button @click="count+=1">+1</button>
    </div>
</template>
<script>
import { ref } from "vue";
export default {
    name: "ref",
    setup() {
        const count = ref("1");
        console.log(count.value);
        count.value++;
        console.log(count.value);
      
        return {
            count
        };
    }
};
</script>

isRef:

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

toRefs

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

<template>
    <div ref="app">
        <div>{{count}}</div>
        <button @click="add">+1</button>
    </div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
    name: "toRefs",
    setup() {
        const state = reactive({
            count: 0
        });
        const add = () => [state.count++];

        return {
            ...toRefs(state),
            add
        };
    }
};
</script>

1.3 computed

computed()用来创建计算属性,computed()函数的返回值是一个ref的实例。

创建只读的计算属性:

在调用computed()函数期间,传入一个函数,可以得到一个只读的计算属性

创建可读可写的计算属性:

在调用computed()函数期间,传入一个包含get和set的对象,可以得到一个可读可写的计算属性。

<template>
    <div class="computed">
        {{computedCountReadOnly}}
        <button @click="refCountReadOnly+=1">+1</button>
        <div>{{computedCountReadWrite}}</div>
    </div>
</template>
<script>
import { ref, computed } from "vue";
export default {
    setup() {
        const refCountReadOnly = ref(0);
        // 只读的计算属性
        const computedCountReadOnly = computed(
            () => refCountReadOnly.value + 1
        );
        // computedCountReadOnly.value = 9; // 这行代码会报错

        // 可读可写的计算属性
        const refCountReadWrite = ref(0);
        const computedCountReadWrite = computed({
            set: val => {
                refCountReadWrite.value = val - 1;
            },
            get: () => refCountReadWrite.value + 1
        });
        computedCountReadWrite.value = 11;

        return {
            refCountReadOnly,
            computedCountReadOnly,
            refCountReadWrite,
            computedCountReadWrite
        };
    }
};
</script>

1.4 Watch

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

基本用法:

const refCount = ref(0);

watch(() => console.warn(refCount.value));

setInterval(() => {
  refCount.value++;
}, 1000);

监视多个数据源:

// reactive
const state = reactive({ count: 0, name: "jokul" });
watch(
  [() => state.count, () => state.name],
  ([newCount, newName], [oldCount, oldName]) => {
    console.log(`reactive $count 旧值:${oldCount}; 新值:${newCount}`);
    console.log(`reactive $name 旧值:${oldName}; 新值:${newName}`);
  },
  {
    lazy: true
  }
);

setTimeout(() => {
  state.count = 27;
  state.name = "Jokul";
}, 2000);

// ref
let refCount = ref(0);
let refName = ref("guohh");
watch(
  [refCount, refName],
  ([newCount, newName], [oldCount, oldName]) => {
    console.log(`ref $count 旧值:${oldCount}; 新值:${newCount}`);
    console.log(`ref $name 旧值:${oldName}; 新值:${newName}`);
  },
  {
    lazy: true
  }
);

setTimeout(() => {
  refCount.value = 27;
  refName.value = "Jokul";
}, 2000);

清除监听:

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

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

在watch中清除无效的异步任务:

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

  • watch被重复执行
  • watch被强制stop
<template>
    <div>
        <input type="text" v-model="keyword" />
    </div>
</template>
import { ref, watch } from "vue";
<script>
setup(){
    const keyword = ref("");

    const asyncprint = val => {
      return setTimeout(() => {
        console.log(val);
      }, 1000);
    };

    watch(
      keyword,
      (newVal, oldVal, clean) => {
        const timer = asyncprint(newVal);
        clean(() => clearTimeout(timer));
      },
      { lazy: true }
    );

    return { keyword };
  }
</script>

1.5 生命周期

新的生命周期函数需要按需导入,并且在setup函数内使用。vue2.x与vue3.x的关系:

  • beforeCreate() ☞ setup()
  • created() ☞ setup()
  • beforeMount() ☞ onBeforeMount()
  • mounted() ☞ onMounted()
  • beforeUpdate() ☞ onBeforeUpdate()
  • updated() ☞ onUpdated()
  • beforeDestory() ☞ onBeforeUnmount()
  • destoryed() ☞ onUnmounted()
  • errorCaptured() ☞ onErrorCaptured()
  onBeforeMount(()=>{
    console.log("onBeforeMount")
  })
  onMounted(()=>{
    console.log("onMounted")
  })
  onBeforeUnmount(()=>{
    console.log("onBeforeUnmount")
  })
  // ...
}

1.5 provide & inject

在vue2.x的时候,我们可以使用provide和inject实现嵌套组件之间的数据传递,但是在vue3.x中需要在setup()函数内使用。

// 父组件
setup() {
  const father = ref("父组件传递的");
  provide("father", father);
}

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

1.6 获取页面DOM或者组件

<template>
    <div ref="divRef">页面dom</div>
</template>
<script>
import { ref, onMounted } from "vue";
export default {
    setup() {
        const divRef = ref(null);

        onMounted(() => {
            divRef.value.style.color = "blue";
        });

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

1.7 defineComponent

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

<script>
import { defineComponent } from "@vue/composition-api";
export default defineComponent({
    props: {
        foo: String
    },
    setup(props) {
        console.warn(typeof props.foo);
    }
});
</script>

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

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

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

弱小和无知不是生存的障碍,傲慢才是。