Vue3.x 中 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 的启发。

HooksMixins
定义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 在组件中调用:

<scriptsetuplang="ts">
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)
        },
    },
}
</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)
* */
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 在组件中使用:

<scriptsetuplang="ts">
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);
    });
});
};
</script>
  • 示例 2:获取鼠标触发点坐标(useMousePosition)

useMousePosition 函数封装:

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

interface MousePosition {
    x: Ref<number>;
    y: Ref<number>;
}

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 在组件中使用:

<template>
<div>
<p>X: {{ x }}</p>
<p>Y: {{ y }}</p>
</div>
</template>
<scriptlang="ts">
import useMousePosition from'@/hooks/useMousePosition'
const{ x, y } = useMousePosition();
</script>
  • 示例 3:封装一个发送短信验证码倒计时 hooks

useMousePosition 函数封装:

<template>
    <div>
        <input type="text" placeholder="请输入验证码" v-model="code">
        <button @click="sendCode">{{ sendBtnText }}</button>
    </div>
</template>

<script lang='ts' setup>
import { ref } from 'vue';

const code = ref('')
const sendBtnText = ref('发送验证码')
const countDownNum = ref(60)
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)
}
</script>
<style lang="css">
button {
    font-size: 14px;
    background: #23A7F2;
    color: #fff;
}

input {
    height: 30px;
}
</style>

这里逻辑很简单,就是点击发送按钮开启定时器出现倒计时的功能

假如我们还有其它地方用到发送短信页面,我们可以将短信发送封装成一个组件。但是如果其它页面想要使用的发送短信页面和这个组件不一样的话,我们就需要将它的逻辑抽离封装成一个 hooks 函数了,下面我们就将这个倒计时功能封装成一名为 useCountDown 的 hooks

import { Ref, ref } from "vue";
export default (
  downNum: number
): ({ sendBtnText: Ref<string>, 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);

七、hooks 函数封装细节归纳

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 将碎片化的响应式变量和方法按功能分块写,实现高内聚低耦合。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/202962.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT人工智能对话系统源码 附完整的搭建教程

人工智能技术的快速发展&#xff0c;对话系统成为了人们与计算机交互的重要方式之一。ChatGPT是一种基于深度学习的大型语言模型&#xff0c;其源码系统可以用于构建各种自然语言处理应用&#xff0c;如聊天机器人、智能客服、语音助手等。 以下是部分代码示例&#xff1a; 系…

41万+账号在抖音卖房获客,如何从中脱颖而出?

随着隔离经济时代的到来&#xff0c;许多传统行业都无法更精准地触达潜在客户&#xff0c;房地产行业也不例外。派单、扫楼、电话等方式已是“地狱级”获客难度&#xff0c;户外、APP广告位也因经费问题陷入困境。 面对营销难&#xff0c;数字化营销是目前很多房企积极探索的新…

【Pytorch】Visualization of Feature Maps(4)——Saliency Maps

学习参考来自 Saliency Maps的原理与简单实现(使用Pytorch实现)https://github.com/wmn7/ML_Practice/tree/master/2019_07_08/Saliency%20Maps Saliency Maps 原理 《Deep Inside Convolutional Networks: Visualising Image Classification Models and Saliency Maps》&…

Latex中多行公式换行及设置编号位置

Latex中多行公式换行及设置编号位置_latex公式换行_泡泡和善意的博客-CSDN博客文章浏览阅读3.2w次&#xff0c;点赞14次&#xff0c;收藏97次。1. 公式换行公式换行的方式有很多种&#xff0c;介绍三种&#xff08;1&#xff09;用equation结合aligned&#xff1a;\begin{equat…

springmvc(基础学习整合)

SpringMVC是Spring框架提供的构建Web应用程序的全功能MVC模块。 在SpringMVC的各个组件中&#xff0c;处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。 springMVC基本介绍&#xff1a; http://t.csdnimg.cn/TOzw9 MVC是一种设计思想&#xff0c;将一个应…

Web端专业级H264/H265 直播流播放器实现-JessibucaPro播放器

概况 这个主要是参加“深圳 liveVideoStack” 的ppt的文字版的分享。 深圳 liveVideoStack 讲师介绍 关于Jessibuca 官网地址&#xff1a;jessibuca.comDemo: DemoDoc&#xff1a;DocGithub地址&#xff1a;Github 关于JessibucaPro 地址&#xff1a;JessibucaProDemo: …

Retrofit中的注解

一、Retrofit中的注解有那些&#xff1f; 方法注解&#xff1a;GET ,POST,PUT,DELETE,PATH,HEAD,OPTIONS,HTTP标记注解&#xff1a;FormUrlEncoded&#xff0c;Multpart&#xff0c;Streaming参数注解&#xff1a;Query&#xff0c;QueryMap&#xff0c;Body&#xff0c;Field…

liunx java 生成图片 中文显示不出来

使用java 生成图片,在图片上打的文字水印显示为一个方框,这种情况的原因,一般是liunx系统或者docker容器内,没有你在打文字水印时选择的字体 解决办法,先找一个免费的字体,比如 Alibaba-PuHuiTi-Regular.otf 然后使用字体 File newFileT new File("Alibaba-PuHuiTi-Re…

pytorch环境下安装node2vec

1.刚开始直接pip install 出错 看到是在安gensim时候出错 2.单独安gensim&#xff1a;https://www.lfd.uci.edu/~gohlke/pythonlibs/ 找到合适的版本&#xff0c;cp36就是python3.6&#xff0c;下载以后放在 3.

Android关于杀掉进程的方案

《风波莫听穿林打叶声》—— 苏轼 〔宋代〕 三月七日&#xff0c;沙湖道中遇雨&#xff0c;雨具先去&#xff0c;同行皆狼狈&#xff0c;余独不觉。已而遂晴&#xff0c;故作此词。 莫听穿林打叶声&#xff0c;何妨吟啸且徐行。 竹杖芒鞋轻胜马&#xff0c;谁怕&#xff1f;一蓑…

开放远程访问MySQL的权限

访问远程数据库时&#xff0c;产生Access denied for user ‘root‘‘xxx.xxx.xxx.xxx‘ (using password: YES)异常的解决办法 一. 异常现象 我编写了一个SpringBoot项目&#xff0c;项目中连接的数据库服务器地址是192.168.87.107&#xff0c;然后打包生成了对应的jar包&am…

Flutter应用程序的加固原理

在移动应用开发中&#xff0c;Flutter已经成为一种非常流行的技术选项&#xff0c;可以同时在Android和iOS平台上构建高性能、高质量的移动应用程序。但是&#xff0c;由于其跨平台特性&#xff0c;Flutter应用程序也面临着一些安全风险&#xff0c;例如反编译、代码泄露、数据…

【Openstack Train安装】九、Nova安装

Nova是OpenStack中最核心的组件&#xff0c;它负责根据需求提供虚拟机服务并管理虚拟机生命周期&#xff0c;包括虚拟机创建、虚拟机调度和热迁移等。 Nova的子组件包括nova-api、nova-compute、nova-scheduler、nova-conductor、nova-db、nova-console等等。 本文介绍Nova安装…

Windows11编译Hadoop3.3.6源码

由于https://github.com/kontext-tech/winutils还未发布3.3.6版本&#xff0c;因此尝试源码编译 目录 环境和安装包准备&#xff0c;见2zlib编译方法一&#xff1a;方法二&#xff1a; 配置文件更改1. maven阿里云镜像2. Node版本3. 越过Javadoc检查 编译HadoopError,其他报错…

每日一题:LeetCode-283. 移动零

每日一题系列&#xff08;day 08&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

一种使用热成像和自动编码器和 3D-CNN 模型堆叠集成进行跌倒检测的新方法

A Novel Approach for Fall Detection Using Thermal Imaging and a Stacking Ensemble of Autoencoder and 3D-CNN Models A Novel Approach for Fall Detection Using Thermal Imaging and a Stacking Ensemble of Autoencoder and 3D-CNN Models:一种使用热成像和自动编码器…

linux用户管理_用户和组

2 用户管理 2.1 用户和组的基本概念和作用 2.1.1 账户实质 账户实质就是一个用户在系统上的标识&#xff0c;系统依据账户ID来区分每个用户的文件、进程、任务&#xff0c;给每个用户提供特定的工作关键&#xff08;如用户的工作目录、SHELL版本以及环境配置等&#xff09;&…

ubuntu终端代理配置

ubuntu浏览器的无需手动设置,主要解决在终端中的配置问题,按照下面配置后可能会ping不通一些ip,但wget/git都是可以的,具体原因以后再分析 查找端口 首先要找到自己代理对应的HTTP端口,以QV2ray软件作为示例,我为8889 手动配置 # 配置系统proxy export http_proxy=1…

怎么一键批量转换PDF/图片为Excel、Word,从而提高工作效率?

在处理大量PDF、图片文件时&#xff0c;我们往往需要将这些文件转换成Word或Excel格式以方便编辑和统计分析。此时&#xff0c;金鸣表格文字识别大师这款工具可以发挥巨大作用。下面&#xff0c;我们就来探讨如何使用它进行批量转换&#xff0c;以实现高效处理。 一、准备工作…