最前端|一文详解Vue3.x 中 hooks 函数封装和使用

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、hooks 是什么

vue3 中的 hooks 就是函数的一种写法就是将文件的一些单独功能的 js 代码进行抽离出来进行封装使用。

它的主要作用是 Vue3 借鉴了 React 的一种机制用于在函数组件中共享状态逻辑和副作用从而实现代码的可复用性。

注意其实 hooks 和 vue2 中的 mixin 有点类似但是相对 mixins 而言 hooks 更清楚复用功能代码的来源, 更清晰易懂。

二、hooks 的优点

  • hooks 作为独立逻辑的组件封装其内部的属性、函数等和外部组件具有响应式依附的作用。
  • 自定义 hook 的作用类似于 vue2 中的 mixin 技术使用方便易于上手。
  • 使用 Vue3 的组合 API 封装的可复用高内聚低耦合。

三、自定义 hook 需要满足的规范

  1. 具备可复用功能才需要抽离为 hooks 独立文件
  2. 函数名/文件名以 use 开头形如: useXX
  3. 引用时将响应式变量或者方法显式解构暴露出来

示例如下

const{ nameRef, Fn } = useXX()

四、hooks 和 utils 区别

  • 相同点

通过 hooks 和 utils 函数封装 可以实现组件间共享和复用提高代码的可重用性和可维护性。

  • 异同点

  1. 表现形式不同hooks 是在 utils 的基础上再包一层组件级别的东西(钩子函数等)utils 一般用于封装相应的逻辑函数没有组件的东西
  2. 数据是否具有响应式hooks 中如果涉及到 refreactivecomputed 这些 api 的数据是具有响应式的而 utils 只是单纯提取公共方法就不具备响应式
  3. 作用范围不同hooks 封装可以将组件的状态和生命周期方法提取出来并在多个组件之间共享和重用utils 通常是指一些辅助函数或工具方法用于实现一些常见的操作或提供特定功能。

  • 总结

utils 是通用的工具函数而 hooks 是对 utils 的一种封装用于在组件中共享状态逻辑和副作用。

通过使用 hooks您可以简化代码并使其更具可读性和可维护性。

五、hooks 和 mixin 区别

  • 相同点

hooks 和 mixin都是常用代码逻辑抽离手段方便进行代码复用

  • 异同点

  1. 语法和用法不同Hooks 是在 Vue 3 的 Composition API 中引入的一种函数式编程的方式而 Mixins 是在 Vue 2 中的一种对象混入机制。Hooks 使用函数的方式定义和使用而 Mixins 则是通过对象的方式进行定义和应用。
  2. 组合性和灵活性不同Hooks 允许开发者根据逻辑功能来组合代码封装为自定义 Hook 函数提高代码复用率。而 Mixins 在组件中的属性和方法会与组件本身的属性和方法进行合并可能会导致命名冲突或不可预料的行为。
  3. 响应式系统不同Vue 3 的 Composition API 使用了一个新的响应式系统可以通过 reactive 和 ref 来创建响应式数据可以更精确地控制组件的更新和依赖追踪。而 Mixins 使用 Vue 2 的响应式系统对数据的追踪和更新较为简单可能存在一些性能上的问题。
  4. 生命周期钩子不同在 Vue 3 的 Composition API 中可以使用 onMounted、onUpdated 等钩子函数来替代 Vue 2 中的生命周期钩子可以更灵活地管理组件的生命周期。Mixins 依然使用 Vue 2 的生命周期钩子。

  • mixins 的优缺点

优点组件中相同代码逻辑复用

缺点

  1. 变量来源不明确变量来源不明确隐式传入不利于阅读使代码变得难以维护。
  2. 命名冲突多个 mixins 的生命周期会融合到一起运行但是同名属性、同名方法无法融合可能会导致冲突。
  3. 滥用会造成维护问题mixins 和组件可能出现多对多的关系复杂度较高即一个组件可以引用多个 mixins一个 mixins 也可以被多个组件引用。

VUE3 提出的 Composition API 旨在解决这些问题。mixins 的缺点是 Composition API 背后的主要动因之一Composition API 受到 React Hooks 的启发。

hooks 代码

useCount.ts 函数示例

import{ ref, onMounted, computed } from'vue';

exportdefaultfunctionuseCount{

constcount = ref(0);

constdoubleCount = computed(
()=>count.value * 2
);

constincrease = (delta) =>{
returncount.value + delta;
}

return{
count,
doubleCount,
increase
};

}

useCount 在组件中调用

importuseCount from"@/hooks/useCount";
const{(count, doubleCount, increase)} = useCount;
constnewCount = increase(10); // 输出: 10

Mixins 的代码:

exportdefaultconstcountMixin = {
data() {
return{
count: 0
};
},
computed: {
doubleCount() {
returnthis.count * 2;
}
},
methods: {
increase(delta){
returnthis.count + delta;
}
};

Mixins 在组件中调用

<scriptsetuplang="ts">
importcountMixin from'@/mixin/countMixin'
exportdefault{
mixins: [countMixin],
mounted() {
console.log(this.doubleCount) // 输出: 0
constnewCount = this.setIncrease(10) // 输出: 10
},
methods: {
setIncrease(count) {
this.increase(count)
},
},
}
</script>

这两个示例展示了使用 Hooks 和 Mixins 的代码风格和组织方式的不同。Hooks 使用函数式的方式来定义逻辑和状态而 Mixins 则是通过对象的方式进行组合和共享代码。

Vue3 自定义 Hooks 是组件下的函数作用域的而 Vue2 时代的 Mixins 是组件下的全局作用域。全局作用域有时候是不可控的就像 var 和 let 这些变量声明关键字一样const 和 let 是 var 的修正。Composition Api 正是对 Vue2 时代 Option Api 高耦合和随处可见 this 的黑盒的修正Vue3 自定义 Hooks 是一种进步。

六、hooks 函数封装示例

  • 示例 1数据导出(useDownload)

useDownload 函数封装

import{ ElNotification } from'element-plus'
/**
* @description 接收数据流生成 blob创建链接下载文件
* @param {any} data 导出的文件blob数据 (必传)
* @param {String} tempName 导出的文件名 (必传)
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {String} fileType 导出的文件格式 (默认为.xlsx)
* */
interfaceuseDownloadParam {
data: any;
tempName: string;
isNotify?: boolean;
fileType?: string;
}

exportconstuseDownload = 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'innavigator)
returnwindow.navigator.msSaveOrOpenBlob(blob, tempName + fileType)
constblobUrl = window.URL.createObjectURL(blob)
constexportFile = 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 在组件中使用

<scriptsetuplang="ts">
import{ useDownload } from"@/hooks/useDownload";

constuserForm = reactive({})
constuserListExport = ()=>{
newPromise(resolve=>{
$Request({
url: $Urls.userListExport,
method: "post",
data: userForm,
responseType: "blob"
}).then((res: any) =>{
useDownload({
data: res.data,
 tempName"用户列表"
});
resolve(res);
});
});
};
</script>

  • 示例 2加减计数(useCount)

useCount 函数封装

import{ computed, ref, Ref } from'vue'
// 定义hook方法
typeCountResultProps = {
count: Ref<number>,
multiple: Ref<number>, // 计算属性
increase: (delta?: number) =>void,
decrease: (delta?: number) =>void,
}

exportdefaultfunctionuseCount(initValue = 1): CountResultProps{
constcount = ref(initValue)

constmultiple = computed(()=>count.value * 2)

constincrease = (delta?: number): void=>{
if(typeofdelta !== 'undefined') {
count.value += delta
} else{
count.value += 1
}
}

constdecrease = (delta?: number): void=>{
if(typeofdelta !== 'undefined') {
count.value -= delta
} else{
count.value -= 1
}
}
return{
count,
increase,
decrease,
multiple,
}
}

useCount 函数在组件中使用

<template>
<p>count:{{count}}</p>
<p>倍数:{{multiple}}</p>
<div>
<button@click="increase(1)">加一</button>
<button@click="decrease(1)">减一</button>// 在模版中直接使用hooks中的方法作为回调函数
</div>
</template>

<scriptsetuplang="ts">
importuseCount from"@/hooks/useCount"
const{count,multiple,increase,decrease} = useCount(10)
</script>

  • 示例 3获取鼠标触发点坐标(useMousePosition)

useMousePosition 函数封装

import{ ref, onMounted, onUnmounted, Ref } from'vue'

interfaceMousePosition {
x: Ref<number>;
y: Ref<number>;
}

exportdefaultfunctionuseMousePosition(): MousePosition{
constx = ref(0)
consty = ref(0)

constupdateMouse = (e: MouseEvent) =>{
x.value = e.pageX
y.value = e.pageY
}

onMounted(()=>{
document.addEventListener('click', updateMouse)
})

onUnmounted(()=>{
document.removeEventListener('click', updateMouse)
})

return{ x, y }
}

useMousePosition 在组件中使用

<template>
<div>
<p>X: {{ x }}</p>
<p>Y: {{ y }}</p>
</div>
</template>
<scriptlang="ts">
importuseMousePosition from'@/hooks/useMousePosition'
const{ x, y } = useMousePosition();
</script>

七、hooks 函数封装细节归纳

1.hooks 函数接收参数写法

写法 1参数通过 props 接收先定义参数类型内部再解构

exportfunctioncommonRequest(params: Axios.AxiosParams) {
let{ url, method, data, responseType = 'json'} = params
}

写法 2接收传参对象先设置默认值再定义参数类型

interfaceDeprecationParam {
from: string;
replacement: string;
type: string;
}
exportconstuseDeprecated = ({ 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 为鼠标按键类型

exportconstuseEscapeKeydown = (handler: (e: KeyboardEvent) => void) =>{}

八、总结

Vue2 时代 Option Api data、methos、watch.....分开写这种是碎片化的分散的代码一多就容易高耦合维护时来回切换代码是繁琐的

Vue3 时代 Composition Api通过利用各种 Hooks 和自定义 Hooks 将碎片化的响应式变量和方法按功能分块写实现高内聚低耦合。


作者 吴冬林 |高级前端开发工程师

版权声明:本文由神州数码云基地团队整理撰写若转载请注明出处。

公众号搜索神州数码云基地了解更多技术干货。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: vue