我们前面刚讲Vue3的时候就说了,这是一个组合式开发,会把数据方法或者监视器等等都放在setup
里面的,目的是为了方便后期维护,就不用在代码行很大的时候来回上下滑动去找方法和数据之间的对应关系,但是如果在setup
里面声明了大量的数据或者方法,那岂不是又回到Vue2的时候,需要去上下滑动去查看代码吗?所以这里就出现了Hooks
。
先说一下定义,简单来说Hooks
创建js
文件或者ts
文件,然后把代码给封装出去,做到极致的模块化。它的应用场景主要有两个:逻辑拆分和功能复用。
Hooks用于逻辑拆分
就像我们开头说的,Vue2中如果代码行很大的话,我们在维护代码的时候可能就要在data
选项,methods
选项或者watch
选项中来回的跳转,就很不方便,所以Vue3就变成组合式开发,出现了setup
,但是同样就是开头说的,如果代码很多的话,setup
里面也会有很多功能逻辑的代码,不同功能的数据方法混合在一起,到时候后期维护也是很不方便,所以Hooks
的第一个应用场景出现了,就是逻辑拆分。把不同功能的代码拆分出来放在独立的js
文件或者ts
文件里,这样这个文件里面就只有只属于这个功能的数据,方法或者监视器了。
比如下面这段代码,里面就有两个功能,一个是给数据+1
,一个是给数据-1
。
html
<template>
{{ numAdd }}
<button @click="addHandle">点击+1</button>
<hr />
{{ numDec }}
<button @click="decHandle">点击-1</button>
</template>
<script lang="ts" setup>
import { ref } from "vue";
let numAdd = ref(0);
let numDec = ref(100);
function addHandle() {
numAdd.value++;
}
function decHandle() {
numDec.value--;
}
</script>

虽然代码不多,但是也可以用Hooks
去区分一下不同功能的代码。首先创建一个Hooks
文件夹,里面存放的就是不同的功能文件,在给文件命名的时候最好用动词+名词的方式,能够简单的描述出这个功能是什么,比如我下面创建的两个文件,一个存放给数据+1
的功能,另一个存放给数据-1
的功能。

addOne.ts
ts
import { ref } from "vue";
// 默认导出一个方法
export default function () {
// 方法里面存放的是功能有关的数据或者方法
let numAdd = ref(0);
function addHandle() {
numAdd.value++;
}
// 把用到的数据或者方法return出去
return { numAdd, addHandle };
}
decOne.ts
ts
import { ref } from "vue";
export default function () {
let numDec = ref(100);
function decHandle() {
numDec.value--;
}
return { numDec, decHandle }
}
然后我们在需要用到的地方引入这两个文件,并且执行引入进来的方法,就可以让方法return
出来数据或者方法,然后再通过ES6
中解构赋值的形式去获取到数据或者方法。
html
<template>
{{ numAdd }}
<button @click="addHandle">点击+1</button>
<hr />
{{ numDec }}
<button @click="decHandle">点击-1</button>
</template>
<script lang="ts" setup>
import addOne from "@/Hooks/addOne";
import decOne from "@/Hooks/decOne";
// 执行引入进来的方法,并使用ES6中解构赋值的方式把需要用到的数据或者方法获取出来
const { numAdd, addHandle } = addOne();
const { numDec, decHandle } = decOne();
</script>
这个时候功能也是能够正常实现的,这里我也明白了为什么Vue3声明响应式数据,相比于Vue2直接在data
选项中直接声明,Vue3还要引入ref
,reactive
这么麻烦,应该都是为了Hooks
,能够在js
文件或者ts
文件声明响应式数据,这只是个人猜测,因为我也是一边写这个笔记一边学习Vue3的。
Vue3的组合式开发,能够实现刚开始说的方便后期维护代码,就是取决于Hooks
,因为Hooks
把某个功能所用到的数据或者方法都集合在一起了,我们后期在维护代码的时候就比较清晰。
Hooks用于功能复用
我们在Vue2的时候经常会创建一个文件夹叫做utils
(可能只有我这么写),里面存放的都是一些公共的方法,这一点我们也可以用Hooks
去实现,把要复用的功能代码抽离出来放在一个文件里面,然后用的时候直接引入这个文件就好了,跟上述情况同理,这里就不赘述了。
Vue3中的Hooks和Vue2中的mixins的区别
看到现在其实不难发现这个Hooks
和Vue2的混入mixins
好像也差不多,但是总归还是有一些不同的,我们现在就来讲一下。
避免了命名冲突
我们知道Vue2的混入是会产生命名冲突的,当组件中变量名或者方法名和混入的变量名或者方法名一样的时候,组件会覆盖混入,而在Vue3的Hooks
中,是不会有命名冲突这种情况的,我们来用上文的代码试一下:

当我们引入数据+1
的功能时,会通过解构赋值的形式去获取功能的数据或者方法,此时如果我们再声明一个相同名称的变量,就会发现会有报错表示这是不允许的,这就很好的避免了命名冲突。当然如果着实需要去声明跟Hooks
里面变量名相同的变量,也可以在解构赋值去获取功能里数据或者方法的时候给它改一下名字,不过一般这种情况很少见。

生命周期冲突的问题
Hooks
里面也是可以写生命周期的,这点跟mixins
也一样,但是mixins
的生命周期如果和组件内的生命周期冲突的话,是先执行mixins
的生命周期,然后再执行组件内的生命周期。而Hooks
的生命周期如果和组件内的生命周期冲突的话,是先执行组件内的生命周期再执行Hooks
的生命周期,如果引入多个Hooks
,就根据解构赋值的顺序来执行不同Hooks
的生命周期。

看代码,组件,addOne
,decOne
,都有onMounted
函数,因为先执行组件内的生命周期,所以先打印组件内的
,又因为是先解构赋值addOne
,所以再打印add
,最后才打印dec
。

如果我们把解构赋值的顺序改变一下,打印的顺序也会跟着改变。

这里是先解构赋值decOne
,所以先打印dec
,再打印add
。

解决了mixins难以追溯属性或方法源头的问题
Vue2中组件如果引入多个mixins
,然后我们再使用其属性或者方法的时候就不知道属于哪个mixins
,就不利于后期维护。
js
export default {
mixins: [ a, b, c, d, e, f, g ], // 引入多个Mixin
mounted() {
console.log(this.xxx) // 问题来了,这个xxx是来自于哪个mixin?
}
}
而Vue3的Hooks
,因为是通过解构赋值的形式去获取它们的数据或者方法,所以就可以很清楚的知道是来源于哪个Hooks
的。
ts
const { numDec, decHandle } = decOne();
const { numAdd, addHandle } = addOne();
Hooks可以传递参数,但是mixins不可以
我们可以调用mixins
的内部方法去传递参数,但是不可以向mixins
本身传递参数。
js
export default {
mixins: [ addMixin, subMixin], // 组件内混入加法和减法Mixin
mounted(){
this.add(num1,num2) // 调用addMixin内部的add方法 这个时候都是可以传参的
this.sub(num1,num2) // 调用subMixin内部的sub方法
// 但是我们不能直接向addMixin传参
}
}
但是Hooks
可以,因为本身就是向外导出的函数,而且我们在解构赋值的时候本身也是要先执行这个函数的,所以Hooks
是可以接收参数的。
总结
Vue2的选项式开发,data,methods,watch,computed...
,如果在代码过多的情况下,后期在维护的时候需要来回切换代码太过于繁琐,可读性很差。
Vue3的组合式开发,通过Hooks
将不同功能的代码抽离出来,将同一个功能的代码集合在一起,方便了后期的维护,读起来比较简单。