【Vue3】Composition API 学习记录

128 阅读9分钟

1 Options API的弊端

Options API的一大特点就是在对应的属性中编写对应的功能模块,比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,还有括生命周期钩子

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

于是在Vue3里出现了Composition API,并在setup( )里面进行开发

2 setup( )

2.1 setup基本介绍

为了开始使用Composition API,需要有一个可以实际使用它(编写代码)的地方; 在Vue组件中,这个位置就是 setup 函数,我们可以用它来替代之前所编写的大部分其他选项; 比如methods、computed、watch、data/生命周期等等

2.2 setup无法使用this和setup的返回值

首先setup中不能像vue2一样来使用this,因为它无法找到组件实例,在setup被调用之前,data、computed、methods等都没有被解析,所以很多以前的写法都会变得不一样(后面再提)

setup中避免使用this 然后setup既然是一个函数,那么它也可以有返回值,setup的返回值可以在模板template中被使用,也就是说如果在setup中定义了一个变量并且通过return来返回,那么它就替代data选项。如果在setup中定义了一个函数并且也通过return来返回,那么它就可以代替methods选项

3 ReactiveAPI、RefAPI、Readonly

上面提到在setup中定义了一个变量并且通过return来返回,那么它就替代data选项,但如果只是定义一个普通的变量并返回,并不会有vue2里定义在data里的变量那种响应式的效果,如果想要类似的响应式,可以用两个API来实现,这俩个API都需要先import引入

ReactiveAPI:

使用reactive函数来处理,在参数里传入一个对象,对象里的属性就会具有响应式,这个函数会返回一个对象比如state,在return里将state返回出去,模板里调用state.属性就可以获得有响应式的值。但是reactive函数传的值要传对象或者数组

RefAPI:

使用ref函数来处理,在参数里可以传基本数据类型,ref会返回一个可变的响应式对象比如counter,这个对象是响应式的引用,所以想获得响应式的值需要通过counter.value的方式来获取。 需要注意的是在setup里想对值进行修改必须要通过counter.value,但在template里面因为有自动浅解包就可以省掉.value,直接写{{counter}}。 不过如果你是把counter放在了一个对象里,然后return那个对象,那在template里就不能解包了,还是得那个对象.counter.value

代码案例:

代码、结果

Readonly:

如果希望某个对象不可以被修改,可以使用Readonly来进行包裹,普通对象或者经过上面API处理的响应式对象都可以进行包裹

readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不 能对其进行修改)。在开发中常见的readonly方法会传入三个类型的参数:①普通对象;②reactive返回的对象;③ref的对象;

借用一张图来展示用法: readonly规则

4 计算属性computed

当我们属性依赖其他状态时,可以使用计算属性来处理,在Vue2中是使用computed选项来完成的,在Composition API中,可以在setup函数中通过导入的computed方法来编写一个计算属性

如何使用computed:

①接收一个getter函数,返回一个不变的 ref 对象

②接收一个具有get和set的对象,返回一个可变的ref对象

代码案例:

      // 1.接收一个getter函数,返回一个不变的ref对象,返回的值就是之前计算属性里要写的东西
      const full = computed(() => name.value + '.' + age.value)

      //2.接收一个具有get和set的对象,返回一个可变的(可读写)ref对象
      const full2 = computed({
        get:()=>{
          return name.value + '.' + age.value //和上面是一个意思,毕竟上面就是接收getter函数
        },
        set:(newValue)=>{
          console.log(newValue)
          const arr = newValue.split('.')
          name.value = arr[0]
          age.value = arr[1]
        }
      })

代码案例、结果

5 数据侦听

在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听

watchEffect用于自动收集响应式数据的依赖

watch需要手动指定侦听的数据源

5.1 watchEffect

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

代码案例:

代码、结果

watchEffect因为会先自动执行一次,执行的时候就会看一下参数里传的函数用到了哪些可响应式对象,然后给他绑定依赖。下面的例子里因为函数里出现了age,所以即使你没有标明说要侦听age,他都会在age变化时调用该函数。而name没出现,所以你改了name,也不会调用

改变执行时机(特殊情况才会用)

watchEffect会在挂载前就默认先执行一次,如果想让他挂载后在执行,可以使用第二个参数。

下面这个代码案例还顺便展示了setup里如何像vue2那样通过绑定ref拿到dom元素:首先定义一个ref对象,通常值设为null,然后在template里通过ref属性绑定某个元素或者组件给刚刚定义的值,这样就可以通过这个定义的值拿到元素或者组件了

代码案例:

代码、结果

取消监听(特殊情况才会用)

代码、结果

5.2 watch

watch侦听函数的数据源有两种类型:

getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref)

直接写入一个可响应式的对象:reactive或者ref(比较常用的是ref)

侦听单个数据源:

getter函数进行监听,不能直接传响应式的reactive对象,要传它的某个属性,否则没法监听。如果你先把reactive对象解构后再包到一个普通对象放到getter函数里,就又可以监听了

直接写入一个可响应式对象,如果写入的是reactive,可以直接传对象,某个属性变化就会监听,不过oldValue和newValue返回的也是reactive对象。如果不想这样就reactive对象解构后再包到一个对象里放到getter函数里,此时既可以保证直接传reactive对象,也可以让返回的是普通对象。 如果写入的是ref,那oldValue和newValue返回的直接是普通对象,因为源码里返回的值已经做好了.value的处理

代码案例: 代码、结果

侦听多个数据源:

第一个参数那里使用一个数组即可,然后将你需要监听的多个数据源全都写到参数里去。数组里如果有reactive对象,你想让他监听的值返回的不是proxy,也可以像上面一样把reactive对象解构后包到普通对象里

代码案例:

代码 结果

watch的深层侦听和立即执行:

watch有第三个参数,也是一个对象。如果我们希望侦听一个深层的侦听,需要在第三个参数的对象里设置deep属性 为true。reactive对象默认会把deep设置为true,但如果你用了解构包普通对象的方式侦听reactive对象,就需要自己手动设置一下deep了。还可以设置immediate 属性让watch立即执行

6 组件通信

props和emit:

父组件传值和以前一样都是写在标签里,子组件接收值也和以前放在props里。只是setup里拿props里的数据因为已经没有this了,所以改为用setup()的第一个参数props.值来拿传过来的值。另外发射事件由于没this改为用setup()的第二个参数的emit属性来发射事件,发射事件前最好在外层emits这个属性里(和props是同级的)注册一下要发射的事件

provide和inject:

改为通过导入的方式直接在setup里使用函数,provide(要传的名称,要传的值)和inject(传的名称)

响应式:

如果我们要传响应式的数据,应该会传ref对象或者reactive对象,但传这种数据共享时子组件有可能会直接修改这些数据,那父组件自然也会跟着变化,如果想保证子组件一定不能修改响应式数据。provide可以在()里传值时,把传的值用readonly包裹一下,这样子组件就无法修改接收到的值了。props好像默认会被readonly包裹

代码案例: image.png image.png

7 封装hook

vue3中一个功能的逻辑可以在setup()里写在一块。当组件复杂时,多个组件中一些重复的逻辑可以被抽取出来。或者当setup()的东西写的比较多时,也可以将一些功能抽取出去。这时就可以将这块功能相关的代码抽离出一个js文件,这样的js文件就是一个hook,一般以use开头进行命名。组件中需要用到这个功能时只要将js文件导入即可,开发人员想知道这一块的功能点进对应的js文件里查看也会一目了然。

封装hook例子

如果引入的hook较多,可以给hook设置一个统一的输出文件,这样在import导入时就会优雅一些

将hook一起导入