您现在的位置是:首页 > 技术教程 正文

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

admin 阅读: 2024-04-01
后台-插件-广告管理-内容页头部广告(手机)

目录

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


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

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

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

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索