vue3中setup函数里的Composition API的详细讲解

38 篇文章 3 订阅
3 篇文章 0 订阅

Options API的弊端

首先我们我们先讲一下vue2 中Options API的弊端,再讲vue3中Composition API 的优势
在Vue2中,我们编写组件的方式是Options API:
Options API的一大特点就是在对应的属性中编写对应的功能模块;
比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命
周期钩子;

但是这种代码有一个很大的弊端:
当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;
当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);

下面我们来看一个非常大的组件,其中的逻辑功能按照颜色进行了划分:
这种碎片化的代码使用理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题;
并且当我们处理单个逻辑关注点时,需要不断的跳到相应的代码块中;

在这里插入图片描述
如果我们能将同一个逻辑关注点相关的代码收集在一起会更好。
这就是Composition API想要做的事情,以及可以帮助我们完成的事情。
也有人把Vue CompositionAPI简称为VCA

认识Composition API

那么既然知道Composition API想要帮助我们做什么事情,接下来看一下到底是怎么做呢?
为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地方;
在Vue组件中,这个位置就是 setup 函数;
setup其实就是组件的另外一个选项:
只不过这个选项强大到我们可以用它来替代之前所编写的大部分其他选项;
比如methods、computed、watch、data、生命周期等等;
接下来我们一起学习这个函数的使用:
函数的参数
函数的返回值

setup函数的参数

我们先来研究一个setup函数的参数,它主要有两个参数:
第一个参数:props
props非常好理解,它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取:对于定义props的类型 ,我们还是和之前的规则是一样的,在props选项中定义.并且在template中依然是可以正常去使用props中的属性,比如message;如果我们在setup函数中想要使用props,那么不可以通过 this 去获取(后面我会讲到为什么). 因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}

但是,因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作:

// MyBook.vue

import { toRefs } from 'vue'

setup(props) {
  const { title } = toRefs(props)

  console.log(title.value)
}

如果 title 是可选的 prop,则传入的 props 中可能没有 title 。在这种情况下,toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef 替代它:

// MyBook.vue
import { toRef } from 'vue'
setup(props) {
  const title = toRef(props, 'title')
  console.log(title.value)
}

第二个参数:context
另外一个参数是context,我们也称之为是一个SetupContext,它里面包含三个属性:
attrs::所有的非prop的attribute;
slots::父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);
emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件

写法一、

// 没有对象结构
      message:{
            typeof:String,
            requier:true
      }
  },
setup(props,context){
      console.log(props.message);
       // Attribute (非响应式对象,等同于 $attrs)
      console.log(context.attrs.class);
      console.log(context.attrs.id);
          // 插槽 (非响应式对象,等同于 $slots)
      console.log(context.slots);
      **加粗样式**
      console.log(context.emit);}

写法二、

    // 参数的对象结构,位置不能变,如果不需要props用_,代替,后面的用解构要啥写啥
    setup(props,{attrs,slots,emit}){
      console.log(props.message);
      console.log(attrs.class);
      console.log(slots);
      console.log(emit);

context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。

// MyBook.vue
export default {
  setup(props, { attrs, slots, emit, expose }) {
    ...
  }
}

attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。请注意,与 props 不同,attrs 和 slots 的 property 是非响应式的。如果你打算根据 attrs 或 slots 的更改应用副作用,那么应该在 onBeforeUpdate 生命周期钩子中执行此操作。

setup函数的返回值

setup既然是一个函数,那么它也可以有返回值,它的返回值用来做什么呢?
setup的返回值可以在模板template中被使用;也就是说我们可以通过setup的返回值来替代data选项;

甚至是我们可以返回一个执行函数来代替在methods中定义的方法:
在这里插入图片描述
在这里插入图片描述
但是,如果我们将 counter 在 increment 或者 decrement进行操作时,是否可以实现界面的响应式呢?
答案是不可以;这是因为对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作;

setup不可以使用this

在 setup() 内部,this 不是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这使得 setup() 在和其它选项式 API 一起使用时可能会导致混淆。表达的含义是this并没有指向当前组件实例;并且在setup被调用之前,data、computed、methods等都没有被解析;所以无法在setup中获取this;

Reactive API(响应式数据API)

如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数:

 setup(){
  //    reactive API对传入的类型是有限制的,它要求我们必须传入的是一个“对象”或者是“数组类型": 如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告
           let title=reactive({
               a:10
           });
           let c =ref(100)
           const foo = ()=>{
               title.a++
               console.log(title.a)        
       }

那么这是什么原因呢?为什么就可以变成响应式的呢?
这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集,当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面),事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的

Ref API(响应式数据API)

reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;
在这里插入图片描述

这个时候Vue3给我们提供了另外一个API:ref API
ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;它内部的值是在ref的 value 属性中被维护的;
在这里插入图片描述

这里有两个注意事项
在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用。但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;

Ref自动解包

模板中的解包是浅层的解包,如果我们的代码是下面的方式:
如果我们将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包:

 <!-- 在模板template中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用;-->
      <h1>{{c}}</h1>
 setup(){
 // ref的对象;
    let c =ref(100)         
 //    但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式
            const foo1 = ()=>{
                c.value++
            }
             return{
             c,
             foo1}
             }

在这里插入图片描述

在这里插入图片描述

认识readonly

我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?
Vue3为我们提供了readonly的方法;readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);
在开发中常见的readonly方法会传入三个类型的参数:
类型一:普通对象;
类型二:reactive返回的对象;
类型三:ref的对象
在这里插入图片描述

readonly的使用

在readonly的使用过程中,有如下规则:

  1. readonly返回的对象都是不允许修改的;

  2. 但是经过readonly处理的原来的对象是允许被修改的;

    比如 const info = readonly(obj),info对象是不允许被修改的。当obj被修改时,readonly返回的info对象也会被修改。但是我们不能去修改readonly返回的对象info。其实本质上就是readonly返回的对象的setter方法被劫持了而已;

readonly的应用

那么这个readonly有什么用呢?
在我们传递给其他组件数据时,往往希望其他组件使用我们传递的内容,但是不允许它们修改时,就可以使用readonly了;
在这里插入图片描述
在这里插入图片描述

Reactive判断的API

isProxy
检查对象是否是由 reactive 或 readonly创建的 proxy。
isReactive
检查对象是否是由 reactive创建的响应式代理: 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
isReadonly
检查对象是否是由 readonly 创建的只读代理。
toRaw
返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
shallowReadonly
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的

toRefs

toRefs和toRef的对象必须是响应式的,如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改解构后的变量,还是修改reactive。返回的state对象,数据都不再是响应式的
在这里插入图片描述

那么有没有办法让我们解构出来的属性是响应式的呢?
Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref,那么我们再次进行结构出来的 name 和 age 本身都是 ref(响应式)的;
在这里插入图片描述
这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化

toRef

如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
在这里插入图片描述

ref其他的API

unref
如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
如果参数是一个 ref,则返回内部值,否则返回参数本身;
这是 val = isRef(val) ? val.value : val 的语法糖函数;
isRef
判断值是否是一个ref对象。
shallowRef
创建一个浅层的ref对象;
triggerRef
手动触发和 shallowRef 相关联的副作用

customRef

如何创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制?
它需要一个工厂函数,该函数接受 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象;
这里我们使用一个的案例:
对双向绑定的属性进行debounce(节流)的操作

customRef的案例

app.vue

<template>
  <div class="app">
      <input v-model="message"/>
      <h2>{{message}}</h2>
  </div>
</template>

<script>
import debounceRef from './hook/useDebounceRef'
export default {
  setup(){
    const message = debounceRef("zayyo")
    return{
      message
    }
  }
 }
</script>

<style  scoped>

</style>

useDebounceRef.js

// 导入customRef函数
import {customRef} from 'vue';

// 自定义ref
// 对函数传入参数value和delay(延迟时间)默认1秒
export default function(value,delay=1000){
    // 定义timer默认为null
    let timer = null;
    // 默认返回customRef两个参数track和trigger
    return customRef((track,trigger) =>{
        return{
            // 默认返回gent函数和set函数
            get(){
                track();
                // 拿到依赖
                return value
            },
            set(newValue){
                // 拿到新值,当继续输入的时候会把时间清除
                clearTimeout(timer);
                // 设置延时
                timer = setTimeout(() => {
                    // 赋予性质
                    value = newValue;
                    trigger();
                }, delay);
            }
        }
    })

}

目录结构
在这里插入图片描述

computed

在前面我们讲解过计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理,在前面的Options API中,我们是使用computed选项来完成的。在Composition API中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性。
如何使用computed呢?
方式一:接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象;
在这里插入图片描述

方式二:接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象;
在这里插入图片描述

侦听数据的变化

在前面的Options API中,我们可以通过watch选项来侦听data或者props的数据变化,当数据变化时执行某一些操作。在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听,watchEffect用于自动收集响应式数据的依赖watch需要手动指定侦听的数据源;

watchEffect

当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用 watchEffect。我们来看一个案例:
首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行;
在这里插入图片描述

watchEffect的停止侦听

如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可。比如在上面的案例中,我们age达到20的时候就停止侦听
在这里插入图片描述

watchEffect清除副作用

什么是清除副作用呢?
比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数;我们可以在传入的回调函数中,执行一些清楚工作;
在这里插入图片描述

setup中使用ref

在讲解 watchEffect执行时机之前,我们先补充一个知识:在setup中如何使用ref或者元素或者组件?
其实非常简单,我们只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可
在这里插入图片描述

watchEffect的执行时机

默认情况下,组件的更新会在副作用函数执行之前:如果我们希望在副作用函数中获取到元素,是否可行呢?
在这里插入图片描述
在这里插入图片描述
我们会发现打印结果打印了两次:这是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null;而当DOM挂载时,会给title的ref对象赋值新的值,副作用函数会再次执行,打印出来对应的元素;

调整watchEffect的执行时机

如果我们希望在第一次的时候就打印出来对应的元素呢?
这个时候我们需要改变副作用函数的执行时机;它的默认值是pre,它会在元素 挂载 或者 更新 之前执行;所以我们会先打印出来一个空的,当依赖的title发生改变时,就会再次执行一次,打印出元素;我们可以设置副作用函数的执行时机:
在这里插入图片描述
flush 选项还接受 sync,这将强制效果始终同步触发。然而,这是低效的,应该很少需要。

Watch的使用

watch的API完全等同于组件watch选项的Property:watch需要侦听特定的数据源,并在回调函数中执行副作用;默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;与watchEffect的比较,watch允许我们:懒执行副作用(第一次不会直接执行);更具体的说明当哪些状态发生变化时,触发侦听器的执行;访问侦听状态变化前后的值

侦听单个数据源

watch侦听函数的数据源有两种类型:一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref);直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref);
在这里插入图片描述
在这里插入图片描述

侦听多个数据源

侦听器还可以使用数组同时侦听多个源:
在这里插入图片描述

侦听响应式对象

如果我们希望侦听一个数组或者对象,那么可以使用一个getter函数,并且对可响应对象进行解构:
在这里插入图片描述

watch的选项

如果我们希望侦听一个深层的侦听,那么依然需要设置 deep 为true:
也可以传入 immediate 立即执行
在这里插入图片描述
生命周期钩子
我们前面说过 setup 可以用来替代 data 、 methods 、 computed 、watch 等等这些选项,也可以替代 生命周
期钩子。
那么setup中如何使用生命周期函数呢?
可以使用直接导入的 onX 函数注册生命周期钩子;
在这里插入图片描述
大家看了上面的表,一定很好奇,为什么beforeCreate和created后面的Hook inside setup是Not needed吧
在这里插入图片描述

在这里插入图片描述

Provide函数

事实上我们之前还学习过Provide和Inject,Composition API也可以替代之前的 Provide 和 Inject 的选项。我们可以通过 provide来提供数据:可以通过 provide 方法来定义每个 Property;
provide可以传入两个参数:name:提供的属性名称;value:提供的属性值;
在这里插入图片描述

Inject函数

在 后代组件 中可以通过 inject 来注入需要的属性和对应的值:可以通过 inject 来注入需要的内容;
inject可以传入两个参数: 要 inject 的 property 的 name; 默认值
在这里插入图片描述

为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 ref 和 reactive。
在这里插入图片描述

修改响应式Property

如果我们需要修改可响应的数据,那么最好是在数据提供的位置来修改:我们可以将修改方法进行共享,在后代组件中进行调用
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zayyo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值