截至目前,组合式函数应该是在VUE 3应用程序中组织业务逻辑最佳的方法。它让我们可以把一些小块的通用逻辑进行抽离、复用,使我们的代码更易于编写、阅读和维护。
一. 什么是“组合式函数”?
根据官方文档说明,在 Vue 应用的概念中,“组合式函数”是一个利用 Vue 组合式 API 来封装和复用有状态逻辑的函数。
这就意味着,任何有状态逻辑,并且使用了响应式处理的逻辑都可以转换成组合式函数。这和我们平时抽离封装的公共方法还是有一些区别的。我们封装的公共方法往往是无状态的:它在接收一些输入后立刻返回所期望的输出。而组合式函数往往是和状态逻辑关联的。简单的理解就是“**可复用逻辑的集合,专注点分点**”
二. vue3 自定义hooks
1. 为什么Vue3要用自定义Hook?
结论:就是为了让Compoosition Api
更好用更丰满,让写Vue3更畅快! 其实这个问题更深意义是为什么Vue3比Vue2更好!无外呼性能大幅度提升,其实编码体验也是Vue3的优点**Composition Api
的引入(解决Option Api在代码量大的情况下的强耦合)** 让开发者有更好的开发体验。
2. 怎么理解自定义Hooks
以函数形式抽离一些可复用的方法像钩子一样挂着,随时可以引入和调用,实现高内聚低耦合的目标;
- 将可复用功能抽离为外部JS文件
- 函数名/文件名以use开头,形如:useXX
- 引用时将响应式变量或者方法显式解构暴露出来如:
const {nameRef,Fn} = useXX()
(在setup函数解构出自定义hooks的变量和方法)
3. 实例
简单的加减法计算,将加法和减法抽离为2个自定义Hooks,并且相互传递响应式数据
- 加法功能-Hook
import { ref, watch } from 'vue';
const useAdd= ({ num1, num2 }) =>{
const addNum = ref(0)
watch([num1, num2], ([num1, num2]) => {
addFn(num1, num2)
})
const addFn = (num1, num2) => {
addNum.value = num1 + num2
}
return {
addNum,
addFn
}
}
export default useAdd
- 减法功能-Hook
import { ref, watch } from 'vue';
export function useSub ({ num1, num2 }){
const subNum = ref(0)
watch([num1, num2], ([num1, num2]) => {
subFn(num1, num2)
})
const subFn = (num1, num2) => {
subNum.value = num1 - num2
}
return {
subNum,
subFn
}
}
- 加减法计算组件
<template>
<div>
num1:<input v-model.number="num1" style="width:100px" />
<br />
num2:<input v-model.number="num2" style="width:100px" />
</div>
<span>加法等于:{{ addNum }}</span>
<br />
<span>减法等于:{{ subNum }}</span>
</template>
<script setup>
import useAdd from './useAdd.js' //引入自动hook
import { useSub } from './useSub.js' //引入自动hook
const num1 = ref(2)
const num2 = ref(1)
//加法功能-自定义Hook(将响应式变量或者方法形式暴露出来)
const { addNum, addFn } = useAdd({ num1, num2 })
addFn(num1.value, num2.value)
//减法功能-自定义Hook (将响应式变量或者方法形式暴露出来)
const { subNum, subFn } = useSub({ num1, num2 })
subFn(num1.value, num2.value)
</script>
3. 自定义Hooks 还可以传参
接上面的例子
平均功能-Hook
import { ref, watch } from "vue";
export function useAverage(addNum) {
const averageNum = ref(0);
watch(addNum, (addNum) => {
averageFn(addNum);
});
const averageFn = (addNum) => {
averageNum.value = addNum / 2;
};
return {
averageNum,
averageFn,
};
}
组件内:
//加法功能-自定义Hook(将响应式变量或者方法形式暴露出来)
const { addNum, addFn } = useAdd({ num1, num2 })
addFn(num1.value, num2.value)//主动调用,返回最新addNum
//平均功能-自定义Hook- hook传入参数值来其他hook暴露出来的变量
const { averageNum, averageFn} = useAverage(addNum)
averageFn(addNum.value)
三 .自定义Hooks 实现minxins
Vue3自定义Hooks和Vue2时代Mixin的关系
Mixin不足
在 Vue 2 中,mixin 是将部分组件逻辑抽象成可重用块的主要工具。但是,他们有几个问题:
1、Mixin 很容易发生冲突:因为每个 mixin 的 property 都被合并到同一个组件中,所以为了避免 property 名冲突,你仍然需要了解其他每个特性。
2、可重用性是有限的:我们不能向 mixin 传递任何参数来改变它的逻辑,这降低了它们在抽象逻辑方面的灵活性。
Vue2 中的mixins混入器写法缺点
//minxins.js 文件
export default{
data(){
return{
message:'混入对象',
name:'zhangsan000'
}
},
methods: {
logMessage() {
console.log('打印message', this.message);
}
}
}
组件:
// 使用
import minxins from "./common/minxins";
import minxins1 from "./common/minxins1"; // 举例
import minxins2 from "./common/minxins2"; // 举例
export default{
mixins: [minxins, minxins1, minxins2], //可混入多个文件
data(){
return{
message:'混入对象新的',
bar:'bar',
}
},
created(){
this.logMessage(); // 打印 '混入对象新的'
console.log('created组件钩子被调用')
},
上面的代码 可以看出: mixins 的深度合并非常隐式,让代码逻辑更难理解和调试,具体表现为如下几点:
- 数据来源不清晰:当使用了多个minxins时, property来自哪个 mixin变得不清晰,这使追溯实现和理解组件行为变得困难。
- 命名冲突:因为每个特性的属性都被合并到同一个组件中,组件内同名的属性或方法会把mixins里的覆盖掉。
- 可重用性有限: 多个minxin需要依赖共享的 property键名来进行相互作用,这使得它们隐性地耦合在一起。而一个组合式函数的返回值可以作为另一个组合式函数的参数被传入,像普通函数那样。
1. Mixin难以追溯的方法与属性!Vue3自定义Hooks却可以
Vue3自定义Hooks, 引用时将响应式变量或者方法显式暴露出来如:
const {nameRef,Fn} = useXX()
2. Mixin同名变量会被覆盖,Vue3自定义Hook可以在引入的时候对同名变量重命名
- mixin
export default {
mixins: [ addMixin, subMixin], //组件内混入加法和减法Mixin
mounted(){
this.add(num1,num2) //调用加法addMixin内部的add方法
this.sub(num1,num2) //调用减法subMixin内部的sub方法
}
}
如果this.add(num1,num2)
和 this.sub(num1,num2)
计算的结果返回的同名变量totalNum,由于JS单线程,后面引入的会覆盖前面的,totalNum最终是减法sub的值
- Vue3自定义Hooks
如上面例子
//加法功能-自定义Hook(将响应式变量或者方法形式暴露出来)
const { addNum, addFn } = useAdd({ num1, num2 })
addFn(num1.value, num2.value)
//减法功能-自定义Hook (将响应式变量或者方法形式暴露出来)
const { subNum, subFn } = useSub({ num1, num2 })
subFn(num1.value, num2.value)
在Vue3自定义Hooks中,虽然加法和减法Hooks都返回了totalNum,但是利用ES6对象解构很轻松给变量重命名
所以在vue3中是不推荐使用mixins 这种方式
Vue3 composition API写 法写 minxins 类功能
//useHello.js
import { ref } from 'vue';
export function useHello() {
let message = ref('组合函数api');
const logMessage = (val) => {
console.log(val + message.value);
};
return { message, logMessage };
}
组件使用时
<template>
<div class="app-container home">
<p>{{ message }}</p>
</div>
</template>
<script setup name="Index">
import { useHello } from './useHello.js';
let name = ref('hello');
// let message = ref('混入对象新'); //异常提示 无法重新声明块范围变量“message”
const { message, logMessage } = useHello();
onMounted(() => {
logMessage(name.value);
});
</script>
上面代码可以看出:
- 当使用了多个组合式封装的属性时,追溯来源清晰明了。
- 命名冲突会直接提示命名重复了的异常。
- 不存在 Vue2那种 隐式的跨 minxin交流,因为页面引用了重复变量直接会提示。
- 使用函函数式编程, 函数内部的变量在打包的时候,压缩器会把函数内部的变量压成 var a,b,c,d 之类的。而对象中的属性key 值,主流打包工具压缩器没有简化对象key值名字。所以函数式编程打包包体相对会更小一些。
四 总结
Vue2时代Option Api ,data、methos、watch.....
分开写,这种是碎片化的分散的,代码一多就容易高耦合,维护时来回切换代码是繁琐的!
Vue3时代Composition Api
,通过利用各种Hooks和自定义Hooks将碎片化的响应式变量和方法按功能分块写,实现高内聚低耦合
形象的讲法:Vue3自定义Hooks是组件下的函数作用域的,而Vue2时代的Mixins是组件下的全局作用域。全局作用域有时候是不可控的,就像var和let这些变量声明关键字一样,const和let是var的修正。Composition Api正是对Vue2时代Option Api 高耦合和随处可见this的黑盒的修正,Vue3自定义Hooks是一种进步。
把Mixin和自定义Hook进行比较,一个是Option Api的体现,一个是Composition Api的体现。如果能理解高内聚低耦合的思想,那么就能理解为什么Vue3是使用Composition Api,并通过各种自定义Hooks使代码更强壮。
五.补充
由Ref组成的对象
以使用可组合的函数式,同时获得ref和reactive的好处
import { ref,reactive}from 'vue'
function useMouse(){
return {
x:ref(0),
y:ref(0),
}
}
const {x} = useMouse()
const mouse = reactive(useMouse())
mouse.x === x.value //true
1.可以直接使用es6解构其中的Ref使用
2.根据使用方式,当想要自动解包的功能时,可以使用 reactive将其转换为对象
将异步操作转换为“同步”
使用组合式API,我们甚至可以将异步操作转换为“同步”的
异步
const data = await fetch('https://196.198.1.0/use/').then(r=>r.json())
// use data
组合式api
const {data} = await useFetch('https://196.198.1.0/use/').json()
const user_url = computed(()=>data.value?data.value.user_url:'')
先建立数据间的“连结”,然后再等待异步请求返回将数据填充。概念和react 中的SWR(stale-while-revalidate)类似。
状态共享
由于组合式API天然提供的灵活性,状态可以独立于组件被创建并使用
但是不兼容ssr(服务器渲染)
兼容SSR的状态共享
使用provide和inject 来共享应用层面的状态。
六. 引用
vue3中的自定义hook函数