vue3 中的 hooks 就是函数的一种写法,就是将文件的一些单独功能的 js 代码进行抽离出来进行封装使用。
它的主要作用是 Vue3 借鉴了 React 的一种机制,用于在函数组件中共享状态逻辑和副作用,从而实现代码的可复用性。
注意:其实 hooks 和 vue2 中的 mixin 有点类似,但是相对 mixins而言, hooks 更清楚复用功能代码的来源, 更清晰易懂。
示例如下:
const{ nameRef, Fn } = useXX()
通过 hooks 和 utils 函数封装, 可以实现组件间共享和复用,提高代码的可重用性和可维护性。
utils 是通用的工具函数,而 hooks 是对 utils 的一种封装,用于在组件中共享状态逻辑和副作用。
通过使用 hooks,您可以简化代码,并使其更具可读性和可维护性。
hooks 和 mixin,都是常用代码逻辑抽离手段,方便进行代码复用;
优点:组件中相同代码逻辑复用;
缺点:
注:VUE3 提出的 Composition API 旨在解决这些问题。mixins 的缺点是 Composition API 背后的主要动因之一,Composition API 受到 React Hooks 的启发。
Hooks | Mixins | |
定义 | hook是通过Composition API引入的一种新特性,类似于React的hook。 | mixin是一种对Vue组件进行扩展的方式。 |
功能 | 可以组织和重用逻辑。在组件中,我们可以创建和重用复杂的逻辑代码,使得组件的逻辑更加清晰和可维护 | 它可以将组件的代码封装到一个可复用的模块。常用于将公用的代码片段进行抽离,实现复用,使得组件的逻辑更加清晰和可维护。 |
使用 | 使用setup方法,可以组织和复用各类逻辑 | 使用mixin属性,加载公用的代码片段。 |
组织代码效果 | 使用Hooks,我们可以让组件的逻辑函数按功能组织,使得组件的逻辑结构更加清晰。 | 使用Mixins,我们可以将组件的各个生命周期的相关函数统一放在一起,但这样做可能会使得组件的逻辑函数分散在各个生命周期中。 |
冲突问题 | Hooks允许我们命名冲突的功能,从而避免了各种命名冲突。 | Mixins可能会导致函数名冲突。如果两个mixin中包含相同的函数,会导致后一个mixin的函数覆盖先前的函数。 |
难以追踪的来源 | Hooks使用的是函数,所以如果不加注释,可能不太容易找到其来源。 | 在Mixin中,我们可以在每个使用了公用代码片段的地方都用注释表明这段代码的来源,有助于我们更好地追踪和维护代码。 |
Debug困难度 | Hooks有更好的Stack Trace,可以提供更优秀的debug体验。 | 对mixins的支持可能会出现在运行时错误的情况下,无法找到那块代码出错的问题,从而导致调试困难。 |
hooks 代码:
useCount.ts 函数示例:
import{ ref, onMounted, computed } from'vue';
export default function useCount{
constcount = ref(0);
constdoubleCount = computed(
()=>count.value * 2
);
constincrease = (delta) =>{
return count.value + delta;
}
return{
count,
doubleCount,
increase
};
}
useCount 在组件中调用:
import useCount from"@/hooks/useCount";
const {(count, doubleCount, increase)} = useCount;
const newCount = increase(10); // 输出: 10
Mixins 的代码:
export default const countMixin = {
data() {
return{
count: 0
};
},
computed: {
doubleCount() {
return this.count * 2;
}
},
methods: {
increase(delta){
return this.count + delta;
}
};
}
Mixins 在组件中调用:
import count Mixin from'@/mixin/countMixin'
export default{
mixins: [countMixin],
mounted() {
console.log(this.doubleCount) // 输出: 0
constnewCount = this.setIncrease(10) // 输出: 10
},
methods: {
setIncrease(count) {
this.increase(count)
},
},
}
这两个示例展示了使用 Hooks 和 Mixins 的代码风格和组织方式的不同。Hooks 使用函数式的方式来定义逻辑和状态,而 Mixins 则是通过对象的方式进行组合和共享代码。
Vue3 自定义 Hooks 是组件下的函数作用域的,而 Vue2 时代的 Mixins 是组件下的全局作用域。全局作用域有时候是不可控的,就像 var 和 let 这些变量声明关键字一样,const 和 let 是 var 的修正。Composition Api 正是对 Vue2 时代 Option Api 高耦合和随处可见 this 的黑盒的修正,Vue3 自定义 Hooks 是一种进步。
useDownload 函数封装:
import{ ElNotification } from'element-plus'
/**
* @description 接收数据流生成 blob,创建链接,下载文件
* @param {any} data 导出的文件blob数据 (必传)
* @param {String} tempName 导出的文件名 (必传)
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {String} fileType 导出的文件格式 (默认为.xlsx)
* */
interface useDownloadParam {
data: any;
tempName: string;
isNotify?: boolean;
fileType?: string;
}
export const useDownload = async({
data,
tempName,
isNotify = true,
fileType = '.xlsx',
}: useDownloadParam) => {
if(isNotify) {
ElNotification({
title: '温馨提示',
message: '如果数据庞大会导致下载缓慢哦,请您耐心等待!',
type: 'info',
duration: 3000,
})
}
try{
constblob = newBlob([data])
// 兼容 edge 不支持 createObjectURL 方法
if('msSaveOrOpenBlob' in navigator) return window.navigator.msSaveOrOpenBlob(blob, tempName + fileType)
const blobUrl = window.URL.createObjectURL(blob)
const exportFile = document.createElement('a')
exportFile.style.display = 'none'
exportFile.download = `${tempName}${fileType}`
exportFile.href = blobUrl
document.body.appendChild(exportFile)
exportFile.click()
// 去除下载对 url 的影响
document.body.removeChild(exportFile)
window.URL.revokeObjectURL(blobUrl)
} catch(error) {
console.log(error)
}
}
useDownload 在组件中使用:
import { useDownload } from"@/hooks/useDownload";
const userForm = reactive({})
const userListExport = ()=>{
new Promise(resolve=>{
$Request({
url: $Urls.userListExport,
method: "post",
data: userForm,
responseType: "blob"
}).then((res: any) =>{
useDownload({
data: res.data,
tempName:"用户列表"
});
resolve(res);
});
});
};
useMousePosition 函数封装:
import { ref, onMounted, onUnmounted, Ref } from'vue'
interface MousePosition {
x: Ref;
y: Ref;
}
export default function useMousePosition(): MousePosition{
constx = ref(0)
consty = ref(0)
const updateMouse = (e: MouseEvent) =>{
x.value = e.pageX
y.value = e.pageY
}
onMounted(()=>{
document.addEventListener('click', updateMouse)
})
onUnmounted(()=>{
document.removeEventListener('click', updateMouse)
})
return{ x, y }
}
useMousePosition 在组件中使用:
X: {{ x }}
Y: {{ y }}
import useMousePosition from'@/hooks/useMousePosition'
const{ x, y } = useMousePosition();
useMousePosition 函数封装:
这里逻辑很简单,就是点击发送按钮开启定时器出现倒计时的功能
假如我们还有其它地方用到发送短信页面,我们可以将短信发送封装成一个组件。但是如果其它页面想要使用的发送短信页面和这个组件不一样的话,我们就需要将它的逻辑抽离封装成一个 hooks 函数了,下面我们就将这个倒计时功能封装成一名为 useCountDown 的 hooks
import { Ref, ref } from "vue";
export default (
downNum: number
): ({ sendBtnText: Ref, sendCode: () => void }) => {
const sendBtnText = ref("发送验证码");
const countDownNum = ref(downNum);
//这里省略调用发送短信接口逻辑,省略禁止点击逻辑
const sendCode = () => {
sendBtnText.value = countDownNum.value + "s";
const timer = setInterval(() => {
countDownNum.value--;
sendBtnText.value = countDownNum.value + "s";
if (countDownNum.value === 0) {
clearInterval(timer);
sendBtnText.value = "发送验证码";
countDownNum.value = 60;
}
}, 1000);
};
return { sendBtnText, sendCode };
};
逻辑和上面一样,只不过是返回了一个按钮要显示的文本以及一个点击发送验证码调用的函数。然后在组件中使用
import { ref } from "vue";
import useCountDown from "../hooks/useCountDown";
const code = ref("");
const { sendBtnText, sendCode } = useCountDown(60);
1.hooks 函数接收参数写法;
写法 1:参数通过 props 接收,先定义参数类型,内部再解构;
export function commonRequest(params: Axios.AxiosParams) {
let{ url, method, data, responseType = 'json'} = params
}
写法 2:接收传参对象,先设置默认值,再定义参数类型
interface DeprecationParam {
from: string;
replacement: string;
type: string;
}
export const useDeprecated = ({ from, replacement, type= 'API' }: DeprecationParam) =>{}
2.解构重命名写法
// setup中
const { list:goodsList, getList:getGoodsList } = useList(axios.get('/url/get/goods'))
const { list:recommendList, getList:getRecommendList } = useList(
axios.get('/url/get/recommendGoods')
)
3.KeyboardEvent 为鼠标按键类型
export const useEscapeKeydown = (handler: (e: KeyboardEvent) => void) =>{}
Vue2 时代 Option Api ,data、methos、watch.....分开写,这种是碎片化的分散的,代码一多就容易高耦合,维护时来回切换代码是繁琐的!
Vue3 时代 Composition Api,通过利用各种 Hooks 和自定义 Hooks 将碎片化的响应式变量和方法按功能分块写,实现高内聚低耦合。