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


    目录

    一、hooks 是什么

    二、hooks 的优点

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

    四、hooks 和 utils 区别

    五、hooks 和 mixin 区别

    六、hooks 函数封装示例

    七、hooks 函数封装细节归纳

    八、总结


    一、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 中如果涉及到 ref,reactive,computed 这些 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 函数示例:

    1. import{ ref, onMounted, computed } from'vue';
    2. exportdefaultfunctionuseCount{
    3. constcount = ref(0);
    4. constdoubleCount = computed(
    5. ()=>count.value * 2
    6. );
    7. constincrease = (delta) =>{
    8. returncount.value + delta;
    9. }
    10. return{
    11. count,
    12. doubleCount,
    13. increase
    14. };
    15. }

    useCount 在组件中调用:

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

    Mixins 的代码:

    1. exportdefaultconstcountMixin = {
    2. data() {
    3. return{
    4. count: 0
    5. };
    6. },
    7. computed: {
    8. doubleCount() {
    9. returnthis.count * 2;
    10. }
    11. },
    12. methods: {
    13. increase(delta){
    14. returnthis.count + delta;
    15. }
    16. };

    Mixins 在组件中调用:

    1. <scriptsetuplang="ts">
    2. importcountMixin from'@/mixin/countMixin'
    3. exportdefault{
    4. mixins: [countMixin],
    5. mounted() {
    6. console.log(this.doubleCount) // 输出: 0
    7. constnewCount = this.setIncrease(10) // 输出: 10
    8. },
    9. methods: {
    10. setIncrease(count) {
    11. this.increase(count)
    12. },
    13. },
    14. }
    15. </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 函数封装:

    1. import{ ElNotification } from'element-plus'
    2. /**
    3. * @description 接收数据流生成 blob,创建链接,下载文件
    4. * @param {any} data 导出的文件blob数据 (必传)
    5. * @param {String} tempName 导出的文件名 (必传)
    6. * @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
    7. * @param {String} fileType 导出的文件格式 (默认为.xlsx)
    8. * */
    9. interfaceuseDownloadParam {
    10. data: any;
    11. tempName: string;
    12. isNotify?: boolean;
    13. fileType?: string;
    14. }
    15. exportconstuseDownload = async({
    16. data,
    17. tempName,
    18. isNotify = true,
    19. fileType = '.xlsx',
    20. }: useDownloadParam) => {
    21. if(isNotify) {
    22. ElNotification({
    23. title: '温馨提示',
    24. message: '如果数据庞大会导致下载缓慢哦,请您耐心等待!',
    25. type: 'info',
    26. duration: 3000,
    27. })
    28. }
    29. try{
    30. constblob = newBlob([data])
    31. // 兼容 edge 不支持 createObjectURL 方法
    32. if('msSaveOrOpenBlob'innavigator)
    33. returnwindow.navigator.msSaveOrOpenBlob(blob, tempName + fileType)
    34. constblobUrl = window.URL.createObjectURL(blob)
    35. constexportFile = document.createElement('a')
    36. exportFile.style.display = 'none'
    37. exportFile.download = `${tempName}${fileType}`
    38. exportFile.href = blobUrl
    39. document.body.appendChild(exportFile)
    40. exportFile.click()
    41. // 去除下载对 url 的影响
    42. document.body.removeChild(exportFile)
    43. window.URL.revokeObjectURL(blobUrl)
    44. } catch(error) {
    45. console.log(error)
    46. }
    47. }

    useDownload 在组件中使用:

    1. <scriptsetuplang="ts">
    2. import{ useDownload } from"@/hooks/useDownload";
    3. constuserForm = reactive({})
    4. constuserListExport = ()=>{
    5. newPromise(resolve=>{
    6. $Request({
    7. url: $Urls.userListExport,
    8. method: "post",
    9. data: userForm,
    10. responseType: "blob"
    11. }).then((res: any) =>{
    12. useDownload({
    13. data: res.data,
    14. tempName:"用户列表"
    15. });
    16. resolve(res);
    17. });
    18. });
    19. };
    20. </script>

    • 示例 2:加减计数(useCount)

    useCount 函数封装:

    1. import{ computed, ref, Ref } from'vue'
    2. // 定义hook方法
    3. typeCountResultProps = {
    4. count: Ref<number>,
    5. multiple: Ref<number>, // 计算属性
    6. increase: (delta?: number) =>void,
    7. decrease: (delta?: number) =>void,
    8. }
    9. exportdefaultfunctionuseCount(initValue = 1): CountResultProps{
    10. constcount = ref(initValue)
    11. constmultiple = computed(()=>count.value * 2)
    12. constincrease = (delta?: number): void=>{
    13. if(typeofdelta !== 'undefined') {
    14. count.value += delta
    15. } else{
    16. count.value += 1
    17. }
    18. }
    19. constdecrease = (delta?: number): void=>{
    20. if(typeofdelta !== 'undefined') {
    21. count.value -= delta
    22. } else{
    23. count.value -= 1
    24. }
    25. }
    26. return{
    27. count,
    28. increase,
    29. decrease,
    30. multiple,
    31. }
    32. }

    useCount 函数在组件中使用:

    1. <template>
    2. <p>count:{{count}}</p>
    3. <p>倍数:{{multiple}}</p>
    4. <div>
    5. <button@click="increase(1)">加一</button>
    6. <button@click="decrease(1)">减一</button>// 在模版中直接使用hooks中的方法作为回调函数
    7. </div>
    8. </template>
    9. <scriptsetuplang="ts">
    10. importuseCount from"@/hooks/useCount"
    11. const{count,multiple,increase,decrease} = useCount(10)
    12. </script>

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

    useMousePosition 函数封装:

    1. import{ ref, onMounted, onUnmounted, Ref } from'vue'
    2. interfaceMousePosition {
    3. x: Ref<number>;
    4. y: Ref<number>;
    5. }
    6. exportdefaultfunctionuseMousePosition(): MousePosition{
    7. constx = ref(0)
    8. consty = ref(0)
    9. constupdateMouse = (e: MouseEvent) =>{
    10. x.value = e.pageX
    11. y.value = e.pageY
    12. }
    13. onMounted(()=>{
    14. document.addEventListener('click', updateMouse)
    15. })
    16. onUnmounted(()=>{
    17. document.removeEventListener('click', updateMouse)
    18. })
    19. return{ x, y }
    20. }

    useMousePosition 在组件中使用:

    1. <template>
    2. <div>
    3. <p>X: {{ x }}</p>
    4. <p>Y: {{ y }}</p>
    5. </div>
    6. </template>
    7. <scriptlang="ts">
    8. importuseMousePosition from'@/hooks/useMousePosition'
    9. const{ x, y } = useMousePosition();
    10. </script>

    七、hooks 函数封装细节归纳

    1.hooks 函数接收参数写法;

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

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

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

    1. interfaceDeprecationParam {
    2. from: string;
    3. replacement: string;
    4. type: string;
    5. }
    6. exportconstuseDeprecated = ({ from, replacement, type= 'API' }: DeprecationParam) =>{}

    2.解构重命名写法

    1. // setup中
    2. const { list:goodsList, getList:getGoodsList } = useList(axios.get('/url/get/goods'))
    3. const { list:recommendList, getList:getRecommendList } = useList(
    4. axios.get('/url/get/recommendGoods')
    5. )

    3.KeyboardEvent 为鼠标按键类型

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

    八、总结

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

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


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

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

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

  • 相关阅读:
    IDL学习:语法基础-字符串
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java成绩管理与学情分析系统11yzf
    mac连wifi不停提示密码【拔掉拓展坞】解决✓
    社区论坛小程序系统源码+自定义设置+活动奖励 自带流量主 带完整的搭建教程
    ios面试准备 - objective-c篇
    猿创征文|从酒店前台收银到软件研发教学主管到技术经理之路~
    windows10不支持Miracast无线投屏(不能进行无线投影)
    企业全面云化的时代——云数据库的未来
    Makefile 变量值
    面试中的项目管理:如何展示你的组织能力
  • 原文地址:https://blog.csdn.net/CBGCampus/article/details/133877693