当前位置:   article > 正文

VUE3 Composition API详解

vue3 composition api

LifeCycle Hooks

在新版的生命周期函数,可以按需导入到组件中,且只能在setup()函数中使用.

 
  1. import { onMounted, onUnmounted } from 'vue';
  2. export default {
  3. setup () {
  4. onMounted(()=>{
  5. //
  6. });
  7. onUnmounted(()=> {
  8. //
  9. });
  10. }
  11. };

生命周期2.x与Composition之间的映射关系

  • beforeCreate -> use setup()
  • created -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

setup

理解

setup()函数是vue3中专门新增的方法,可以理解为Composition Api的入口.

执行时机

在beforecreate之后,create之前执行.

接收props数据

  1. export default {
  2. props: {
  3. msg: {
  4. type: String,
  5. default: () => {}
  6. }
  7. },
  8. setup(props) {
  9. console.log(props);
  10. }
  11. }

context:

setup()的第二个参数是一个上下文对象,这个上下文对象大致包含了这些属性,注意:在setup()函数中无法访问this

  1. const MyComponent = {
  2. setup(props, context) {
  3. context.attrs
  4. context.slots
  5. context.parent
  6. context.root
  7. context.emit
  8. context.refs
  9. }
  10. }

reactive

reactive是用来创建一个响应式对象,等价于2.x的Vue.observable,具体可以参考下面demo。

  1. <template>
  2. <div>
  3. <p @click="incment()">
  4. click Me!
  5. </p>
  6. <p>
  7. 一:{{ state.count }} 二: {{ state.addCount }}
  8. </p>
  9. </div>
  10. </template>
  11. <script>
  12. import { reactive } from 'vue';
  13. export default {
  14. setup () {
  15. const state = reactive({//创建响应式数据
  16. count: 0,
  17. addCount: 0
  18. });
  19. function incment () {
  20. state.count++;
  21. state.addCount = state.count * 2;
  22. }
  23. return {
  24. state,
  25. incment
  26. };
  27. }
  28. };
  29. </script>

ref

基本语法

ref()函数用来给定的值创建一个响应式的数据对象,ref()的返回值是一个对象,这个对象上只包含一个.value属性.下面是基本数据类型创建步骤.

  1. import { ref, defineComponent } from 'vue';
  2. export default defineComponent ({
  3. setup () {
  4. const valueNumber = ref(0);
  5. const valueString = ref('hello world!');
  6. const valueBoolean = ref(true);
  7. const valueNull = ref(null);
  8. const valueUndefined = ref(undefined);
  9. return {
  10. valueNumber,
  11. valueString,
  12. valueBoolean,
  13. valueNull,
  14. valueUndefined
  15. };
  16. }
  17. });

在template中访问ref创建的响应式数据

  1. import { ref } from 'vue';
  2. export default {
  3. setup () {
  4. const value = ref(1);
  5. return {
  6. value,
  7. msg: 'hello world!'
  8. };
  9. }
  10. };
  1. <template>
  2. <p>
  3. {{ value }} {{ msg }}
  4. </p>
  5. </template>

将ref响应式数据挂载到reactive中

当把ref()创建出来值直接挂载到reactive()中时,会自动把响应式数据对象的展开为原始的值,不需要通过.value就可以直接访问到.

  1. import { ref, reactive } from 'vue';
  2. export default {
  3. setup () {
  4. const count = ref(1);
  5. const state = reactive({
  6. count
  7. });
  8. console.log(state.count);//1 可以直接访问到,不需要通过.value就可以直接访问到
  9. state.count++;
  10. console.log(count.value);//2 我们发现,最初count值也发生了改变
  11. return {
  12. count
  13. };
  14. }
  15. };

新的ref会覆盖旧的ref,实例如下:

  1. import { ref, reactive } from 'vue';
  2. export default {
  3. setup () {
  4. const count = ref(1);
  5. const state = reactive({
  6. count
  7. });
  8. const newCount = ref(9);
  9. state.count = newCount;
  10. state.count++;
  11. console.log(state.count, newCount, count);// 10 10 1
  12. return {
  13. count
  14. };
  15. }
  16. };

我们发现,这次的count值却没有发生改变,还是原始值1,是因为新创建的newCount替换并覆盖了之前的count值,取代了它的位置.

isRef

用来判断某个值是否为ref创建出来的对象,场景:当需要展开某个值可能是ref()创建出来的对象时.

  1. import { ref, isRef } from 'vue';
  2. export default {
  3. setup () {
  4. const count = ref(1);
  5. const unwrappend = isRef(count) ? count.value : count;
  6. return {
  7. count,
  8. unwrappend
  9. };
  10. }
  11. };

toRefs

torefs()函数可以将reactive()创建出来的响应式对象转换为普通的对象,只不过这个对象上的每个属性节点都是ref()类型的响应式数据

  1. <template>
  2. <p>
  3. <!-- 可以不通过state.value去获取每个属性 -->
  4. {{ count }} {{ value }}
  5. </p>
  6. </template>
  7. <script>
  8. import { ref, reactive, toRefs } from 'vue';
  9. export default {
  10. setup () {
  11. const state = reactive({
  12. count: 0,
  13. value: 'hello',
  14. })
  15. return {
  16. ...toRefs(state)
  17. };
  18. }
  19. };
  20. </script>

toRef

概念:为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者是同步的。相当于浅拷贝一个属性.

区别ref: 拷贝的是一份新的数据单独操作,更新时相互不影响,相当于深拷贝。

场景:当要将某个prop的ref传递给某个复合函数时,toRef很有用.

  1. import { reactive, ref, toRef } from 'vue'
  2. export default {
  3. setup () {
  4. const m1 = reactive({
  5. a: 1,
  6. b: 2
  7. })
  8. const m2 = toRef(m1, 'a');
  9. const m3 = ref(m1.a);
  10. const update = () => {
  11. // m1.a++;//m1改变时,m2也会改变
  12. // m2.value++; //m2改变时m1同时改变
  13. m3.value++; //m3改变的同时,m1不会改变
  14. }
  15. return {
  16. m1,
  17. m2,
  18. m3,
  19. update
  20. }
  21. }
  22. }

computed

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

创建只读的计算属性

  1. import { ref, computed } from 'vue';
  2. export default {
  3. setup () {
  4. const count = ref(0);
  5. const double = computed(()=> count.value + 1);//1
  6. double++;//Error: "double" is read-only
  7. return {
  8. count,
  9. double
  10. };
  11. }
  12. };

创建可读可写的计算属性

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

  1. // 创建一个 ref 响应式数据
  2. const count = ref(1)
  3. // 创建一个 computed 计算属性
  4. const plusOne = computed({
  5. // 取值函数
  6. get: () => count.value + 1,
  7. // 赋值函数
  8. set: val => {
  9. count.value = val - 1
  10. }
  11. })
  12. // 为计算属性赋值的操作,会触发 set 函数
  13. plusOne.value = 9
  14. // 触发 set 函数后,count 的值会被更新
  15. console.log(count.value) // 输出 8

watch

watch()函数用来监视某些数据项的变化,从而触发某些特定的操作,看下面这个案例,会实时监听count值的变化. 查看官方文档API

  1. import { ref, watch } from 'vue';
  2. export default {
  3. setup () {
  4. const count = ref(1);
  5. watch(()=>{
  6. console.log(count.value, 'value');
  7. })
  8. setInterval(()=>{
  9. count.value++;
  10. },1000);
  11. return {
  12. count,
  13. };
  14. }
  15. };

监听指定的数据源

监听reactive的数据变化

  1. import { watch, reactive } from 'vue';
  2. export default {
  3. setup () {
  4. const state = reactive({
  5. count: 0
  6. })
  7. watch(()=>state.count,(count, prevCount)=>{
  8. console.log(count, prevCount);//变化后的值 变化前的值
  9. })
  10. setInterval(()=>{
  11. state.count++;
  12. },1000);
  13. return {
  14. state
  15. };
  16. }
  17. };

监听ref类型的数据变化

  1. import { ref, watch } from 'vue';
  2. export default {
  3. setup () {
  4. const count = ref(0);
  5. watch(count,(count, prevCount)=>{
  6. console.log(count, prevCount);//变化后的值 变化前的值
  7. })
  8. setInterval(()=>{
  9. count.value++;
  10. },1000);
  11. return {
  12. count
  13. };
  14. }
  15. };

监听多个指定数据变化

监听reactive类型数据变化

  1. import { watch, reactive } from 'vue';
  2. export default {
  3. setup () {
  4. const state = reactive({
  5. count: 0,
  6. msg: 'hello'
  7. })
  8. watch([()=> state.count, ()=> state.msg],([count, msg], [prevCount, prevMsg])=>{
  9. console.log(count, msg);
  10. console.log('---------------------');
  11. console.log(prevCount, prevMsg);
  12. })
  13. setTimeout(()=>{
  14. state.count++;
  15. state.msg = 'hello world';
  16. },1000);
  17. return {
  18. state
  19. };
  20. }
  21. };

监听ref类型数据变化

  1. import { ref, watch } from 'vue';
  2. export default {
  3. setup () {
  4. const count = ref(0);
  5. const msg = ref('hello');
  6. watch([count, msg],([count, name], [prevCount, prevname])=>{
  7. console.log(count, name);
  8. console.log('---------------------');
  9. console.log(prevCount, prevname);
  10. })
  11. setTimeout(()=>{
  12. count.value++;
  13. msg.value = 'hello world';
  14. },1000);
  15. return {
  16. count,
  17. msg
  18. };
  19. }
  20. };

清除监视

在setup()函数内创建的watch监视,会在当前组件被销毁的时候自动停止。如果想要明确的停止某个监视,可以调用watch()函数的返回值即可

  1. // 创建监视,并得到 停止函数
  2. const stop = watch(() => {
  3. /* ... */
  4. })
  5. // 调用停止函数,清除对应的监视
  6. stop()

清除无效的异步任务

有时候watch()监视的值发生了变化,我们期望清除无效的异步任务,此时watch回调函数中提供了cleanup registrator function来执行清除工作

  • 场景
  • watch被重复执行了
  • watch被强制stop()
  1. <template>
  2. <p>
  3. <input type="text" v-model="keyword">
  4. </p>
  5. </template>
  6. <script>
  7. import { watch, ref } from 'vue';
  8. export default {
  9. setup () {
  10. const keyword = ref('');
  11. const asyncPrint = val => {
  12. return setTimeout(()=>{
  13. console.log(val);
  14. },1000);
  15. }
  16. watch(keyword, (keyword, prevKeyword, onCleanup) => {
  17. const timeId = asyncPrint(keyword);
  18. onCleanup(()=> clearTimeout(timeId));
  19. }, {lazy: true})
  20. return {
  21. keyword
  22. };
  23. }
  24. };
  25. </script>

watchEffect

vue3中新增的api,用于属性监听.

与watch有什么不同?

  • watchEffect不需要指定监听属性,可以自动收集依赖,只要我们回调中引用了响应式的属性,那么这些属性变更的时候,这个回调都会执行,而watch只能监听指定的属性而做出变更(v3中可以同时监听多个)
  • watch可以获取到新值和旧值,而watchEffect获取不到
  • watchEffect会在组件初始化的时候就会执行一次与computed同理,而收集到的依赖变化后,这个回调才会执行,而watch不需要,除非设置了指定参数。

基础用法

  1. import { watchEffect, ref } from 'vue'
  2. setup () {
  3. const userID = ref(0)
  4. watchEffect(() => console.log(userID))
  5. setTimeout(() => {
  6. userID.value = 1
  7. }, 1000)
  8. /*
  9. * LOG
  10. * 0
  11. * 1
  12. */
  13. return {
  14. userID
  15. }
  16. }

停止监听

如果watchEffect是在setup或者生命周期里面注册的话,在取消挂在的时候会自动停止。

  1. //停止监听
  2. const stop = watchEffect(() => {
  3. /* ... */
  4. })
  5. // later
  6. stop()

使 side effect 无效

什么是 side effect ,不可预知的接口请求就是一个 side effect,假设我们现在用一个用户ID去查询用户的详情信息,然后我们监听了这个用户ID, 当用户ID 改变的时候我们就会去发起一次请求,这很简单,用watch 就可以做到。 但是如果在请求数据的过程中,我们的用户ID发生了多次变化,那么我们就会发起多次请求,而最后一次返回的数据将会覆盖掉我们之前返回的所有用户详情。这不仅会导致资源浪费,还无法保证 watch 回调执行的顺序。而使用watchEffect我们就可以做到.

onInvalidate(fn)传入的回调会在watchEffect重新运行或者watchEffect停止的时候执行。

  1. watchEffect(() => {
  2. // 异步api调用,返回一个操作对象
  3. const apiCall = someAsyncMethod(props.userID)
  4. onInvalidate(() => {
  5. // 取消异步api的调用。
  6. apiCall.cancel()
  7. })
  8. })

shallowReactive

概念:只处理对象最外层属性的响应式(也就是浅响应式),所以最外层属性发生改变,更新视图,其他层属性改变,视图不会更新.

场景:如果一个对象的数据结构比较深,但变化只是最外层属性.

  1. import { shallowReactive } from 'vue'
  2. export default {
  3. setup() {
  4. const obj = {
  5. a: 1,
  6. first: {
  7. b: 2,
  8. second: {
  9. c: 3
  10. }
  11. }
  12. }
  13. const state = shallowReactive(obj)
  14. function change1() {
  15. state.a = 7
  16. }
  17. function change2() {
  18. state.first.b = 8
  19. state.first.second.c = 9
  20. console.log(state);
  21. }
  22. return { state }
  23. }
  24. }

shallowRef

概念:只处理了value的响应式,不进行对象的reactive处理.

场景:如果有一个对象数据,后面会产生新的对象替换.

  1. import { shallowRef } from 'vue'
  2. export default {
  3. setup () {
  4. const m1 = shallowRef({a: 1, b: {c: 2}})
  5. const update = () => {
  6. m1.value.a += 1
  7. }
  8. return {
  9. m1,
  10. update
  11. }
  12. }
  13. }

customRef

创建一个自定义的ref,并对其依赖跟踪和更新触发进行显式控制.

场景:使用customRef实现输入框防抖

  1. <template>
  2. <div>
  3. <input v-model="keyword" placeholder="搜索关键字"/>
  4. <p>{{keyword}}</p>
  5. </div>
  6. </template>
  7. <script>
  8. import { customRef } from 'vue'
  9. export default {
  10. setup () {
  11. const keyword = useDebouncedRef('', 500)
  12. console.log(keyword)
  13. return {
  14. keyword
  15. }
  16. }
  17. }
  18. function useDebouncedRef(value, delay = 200) {
  19. let timeout;
  20. return customRef((track, trigger) => {
  21. return {
  22. get() {
  23. // 告诉Vue追踪数据
  24. track()
  25. return value
  26. },
  27. set(newValue) {
  28. clearTimeout(timeout)
  29. timeout = setTimeout(() => {
  30. value = newValue
  31. // 告诉Vue去触发界面更新
  32. trigger()
  33. }, delay)
  34. }
  35. }
  36. })
  37. }
  38. </script>

自定义Hook函数

自定义hook的作用类型于vue2中的mixin技术。

优势:清楚知道代码来源,方便复用

案例:收集用户点击的页面坐标

hook/useMousePosition.js

  1. import { ref, onMounted, onUnmounted } from "vue";
  2. export default function useMousePosition() {
  3. // 初始化坐标数据
  4. const x = ref(-1);
  5. const y = ref(-1);
  6. // 用于收集点击事件坐标的函数
  7. const updatePosition = e => {
  8. x.value = e.pageX;
  9. y.value = e.pageY;
  10. };
  11. // 挂载后绑定点击监听
  12. onMounted(() => {
  13. document.addEventListener("click", updatePosition);
  14. });
  15. // 卸载前解绑点击监听
  16. onUnmounted(() => {
  17. document.removeEventListener("click", updatePosition);
  18. });
  19. return { x, y };
  20. }

模版中使用hook函数

  1. <template>
  2. <div>
  3. <p>{{ x }}</p>
  4. <p>{{ y }}</p>
  5. </div>
  6. </template>
  7. <script>
  8. import useMousePosition from '@/hook/useMousePosition'
  9. export default {
  10. setup () {
  11. const {x, y} = useMousePosition();
  12. return {
  13. x,
  14. y
  15. }
  16. }
  17. }
  18. </script>

readonly与shallowReadonly

  • readonly:
  • 深度只读数据
  • 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
  • 只读代理是深层的:访问的任何嵌套 property 也是只读的。
  • shallowReadonly
  • 浅只读数据
  • 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
  • 应用场景:
  • 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除

  1. <template>
  2. <div @click="update()">
  3. <p>{{ a }}</p>
  4. <p>{{ b }}</p>
  5. </div>
  6. </template>
  7. <script>
  8. import { reactive, readonly, shallowReadonly } from 'vue'
  9. export default {
  10. setup () {
  11. const state = reactive({
  12. a: 1,
  13. b: {
  14. c: 2
  15. }
  16. })
  17. const m = readonly(state);
  18. const m2 = shallowReadonly(state);
  19. const update = () => {
  20. m.a++ //无法修改 只读
  21. m2.a++ //无法修改
  22. m2.b.c++ //可以修改 视图层数据改变
  23. }
  24. return {
  25. ...toRefs(state),
  26. update
  27. }
  28. }
  29. }
  30. </script>

Template refs

通过ref()还可以引用页面上的元素或者组件.

元素引用

使用ref()函数创建DOM引用,需在onMounted中获取.

  1. <template>
  2. <div>
  3. <p ref="dom">hello</p>
  4. </div>
  5. </template>
  6. <script>
  7. import { ref, onMounted } from 'vue';
  8. export default {
  9. setup () {
  10. const dom = ref(null);
  11. onMounted(()=> {
  12. console.log(dom.value)//当前dom元素
  13. });
  14. return {
  15. dom
  16. }
  17. }
  18. };
  19. </script>

组件引用

  1. <template>
  2. <div>
  3. <Test ref="comRef"/>
  4. </div>
  5. </template>
  6. <script>
  7. import { ref, onMounted } from 'vue';
  8. import Test from "./test2";
  9. export default {
  10. components: { Test },
  11. setup () {
  12. const comRef = ref(null);
  13. onMounted(()=> {
  14. comRef.value.coun;//获取子组件值
  15. comRef.value.Handle();//调用子组件函数
  16. })
  17. return {
  18. comRef
  19. }
  20. }
  21. };
  22. </script>

  • test2
  1. <template>
  2. <p>
  3. {{ count }}
  4. </p>
  5. </template>
  6. <script>
  7. import { ref } from 'vue';
  8. export default {
  9. setup () {
  10. const count = ref('count');
  11. const Handle = (()=> {
  12. console.log('hello');
  13. })
  14. return {
  15. count,
  16. Handle
  17. }
  18. }
  19. };
  20. </script>

createComponent

这个函数不是必须的,除非你想完美结合TypeScript提供的类型推断来进行项目开发

场景:这个函数仅仅提供了类型推断,能为setup()函数中的props提供完整的类型推断.

  1. import { createComponent } from 'vue'
  2. export default createComponent({
  3. props: {
  4. foo: String
  5. },
  6. setup(props) {
  7. props.foo // <- type: string
  8. }
  9. })

getCurrentInstance

描述:可以获取当前组件的实例,然后通过ctx属性获取当前上下文,这样我们就可以在steup中使用router和vuex了.

  1. <script>
  2. import { getCurrentInstance } from 'vue'
  3. export default {
  4. setup () {
  5. const { ctx } = getCurrentInstance()
  6. console.log(ctx.$router.currentRoute.value) //当前路径
  7. //与以前this获取原型上东西一样
  8. //ctx.$parent 父组件
  9. // ctx.$nextTick 组件更新完毕
  10. // ctx.$store VueX
  11. }
  12. }
  13. </script>

Teleport

描述:传送门组件提供一种简洁的方式可以指定它里面内容的父元素,允许我们控制`Teleport`的嵌套的内容在DOM中哪个父节点下呈现HTML,而不必求助于全局状态或者拆分为两个组件.

  • to String 必传属性
  • to="#last"
  • to=".last"
  • to="[data-teleport]"
  • disabled Boolean 可选属性
  • 用于禁用teleport的功能,意味着插槽的内容将不会移动到任何位置,而在父组件指定的位置渲染.
  1. <template>
  2. <div>
  3. <button @click="modelOpen = true">点击打开弹窗 </button>
  4. <teleport to="body">
  5. <div v-if="modelOpen" class="model">
  6. <div class="model-body">
  7. 这是一个模态框
  8. <button @click="modelOpen = false">关闭弹窗</button>
  9. </div>
  10. </div>
  11. </teleport>
  12. </div>
  13. </template>
  14. <script>
  15. import { defineComponent, ref } from "vue";
  16. export default defineComponent({
  17. name: 'ModelButton',
  18. setup() {
  19. const modelOpen = ref(false);
  20. return {
  21. modelOpen
  22. }
  23. }
  24. })
  25. </script>
  26. <style scoped>
  27. .model {
  28. position: absolute;
  29. left: 0;
  30. top: 0;
  31. right: 0;
  32. bottom: 0;
  33. background: rgba(0, 0, 0, .3);
  34. display: flex;
  35. align-items: center;
  36. justify-content: center;
  37. }
  38. .model-body {
  39. width: 300px;
  40. height: 250px;
  41. background: #fff;
  42. }
  43. </style>

使用`Teleport`组件,通过props `to`属性指定该组件的渲染位置在body下,但该组件的状态`modelOpen`则是由vue内部组件控制.

Fragments

描述:Fragments作为vue3的新特性之一,允许一个组件可以有多个根节点。

  1. <template>
  2. <header>...</header>
  3. <main v-bind="$attrs">...</main>
  4. <footer>...</footer>
  5. </template>
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号