【Vue3】如何封装一个超级好用的 Hook !

本文将通过介绍什么是 Hook、如何在 Vue 使用 Hook,以及在实践场景中如何封装自己的 Vue Hook,带你走进 Hook 的世界,写出更优雅的代码。

什么是 Hook

Vue3 官方文档是这样定义组合式函数的。A “composable” is a function that leverages Vue’s Composition API to encapsulate and reuse stateful logic.,一个利用 Vue 的组合式 API 来封装和复用具有状态逻辑的函数。
这个概念借鉴自 React 的 Hook。在 16.8 的版本中,React 引入了 React Hook。这是一项特别强大的技术,通过封装有状态的函数,极大提高了组件的编写效率和维护性。在下文中也是使用 Hook 来替代“组合式函数”进行叙述。
在开发中,我们经常会发现一些可以重复利用的代码段,于是我们将其封装成函数以供调用。这类函数包括工具函数,但是又不止工具函数,因为我们可能也会封装一些重复的业务逻辑。以往,在前端原生开发中,我们封装的这些函数都是“无状态”的。为了建立数据与视图之间的联系,基于 MVC 架构的 React 框架和基于 MVVM 的 Vue 框架都引入了“状态”这一概念,状态是特殊的 JavaScript 变量,它的变化会引起视图的变化。
在这类框架中,如果一个变量的变化不会引起视图的变化,那么它就是普通变量,如果一个变量已经被框架注册为状态,那么这个变量的变化就会引发视图的变化,我们称之为响应式变量。如果一个函数包含了状态(响应式变量),那么它就是一个 Hook 函数。
在具备“状态”的框架的基础上,才有 Hook 这一说。Hook 函数与普通函数的本质区别在于是否具备“状态”。
比如,在一个 Vue 项目中,我们可能同时引入了 lodash 库和 VueUse 库,这两个库都是提供一些方便的工具函数。工具函数库只引入一个不行吗,不会重复吗?或许不行,因为 lodash 的函数是无状态的,用来处理普通变量或者响应式变量中的数据部分,而 VueUse 提供的 api 都是 Hook。如果你的项目中既有普通变量又有响应式变量,你或许就会在同一个项目中同时接触到这两个库。
React 官方为我们提供了一些非常方便的 Hook 函数,比如 useState、useEffect(我们通常使用 use 作为前缀来标识 Hook 函数),但是这远远不够,或者说,它们足够通用但是不够具体。为了在具体业务下复用某些逻辑,我们往往会封装自己的 Hook,即自定义 Hook。
为什么这里会反复提到 React 中呢?
因为提到 Hook,就不可能避开 React。Hook 是 React 发扬光大的,使用 Hook 已经是 React 社区的主流。然而,只要框架具备“状态”这一概念,都可以使用 Hook 技术!下面文章将会介绍如何将 Hook 应用到 Vue 当中。

在 Vue 中使用Hook

下面我们来看一个简单的自定义 Hook(来自 Vue 官方文档):
需求:在页面实时显示鼠标的坐标。 实现:没有使用 Hook。

< script setup >
    import {
   
        ref,
        onMounted,
        onUnmounted
    } from 'vue'
const x = ref(0) const y = ref(0)

function update(event) {
   
    x.value = event.pageX y.value = event.pageY
}
onMounted(() =>
    window.addEventListener('mousemove', update)
)
onUnmounted(() =>
    window.removeEventListener('mousemove', update)
) 
< /script>
<template>Mouse position is at: {
  { x }}, {
  { y }}</template>

在没有封装的情况下,如果我们在另一个页面也需要这个功能,我们需要将代码复制过去。另外,可以看出,它声明了两个变量,并且在生命周期钩子 onMounted 和 onUnmounted 中书写了一些代码,如果这个页面需要更多的功能,那么会出现代码中存在很多变量、生命周期中存在很多逻辑写在一起的现象,使得这些逻辑混杂在一起,而使用 Hook 可以将其分隔开来(这也是为什么会有很多人使用 Hook 的原因,分离代码,提高可维护性!)
使用 Hook:

< script setup >
    import {
   
        useMouse
    } from './mouse.js'
const {
   
    x,
    y
} = useMouse() <
    /script>
<template>Mouse position is at: {
  { x }}, {
  { y }}</template>

可以发现,比原来的代码更加简洁,这时如果加入其它功能的变量,也不会觉得眼花缭乱了。
当然,我们需要在外部定义这个 Hook:

// mouse.js
import {
   
    ref,
    onMounted,
    onUnmounted
} from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
   
    // 被组合式函数封装和管理的状态  
    const x = ref(0) const y = ref(0)
    // 组合式函数可以随时更改其状态。  
    function update(event) {
   
        x.value = event.pageX y.value = event.pageY
    }
    // 一个组合式函数也可以挂靠在所属组件的生命周期上  
    // 来启动和卸载副作用  
    onMounted(() => window.addEventListener('mousemove', update)) onUnmounted(() => window.removeEventListener('mousemove', update))
    // 通过返回值暴露所管理的状态  
    return {
   
        x,
        y
    }
}

或许,你可以试着去 VueUse 库找到别人封装好的 useMouse!

import { useMouse } from 'VueUse'

恭喜你,掌握了 VueUse 库的使用方法。如果需要其它 Hook,你可以先试着去官方文档(VueUse | VueUse)(https://vueuse.org/)查找,使用现成的函数,而不是自己去封装。

封装一(入门级的表格 Hook)

在前面,我们介绍完了 Hook 的概念,完成了一个简单的自定义 Hook,还学会了使用社区提供的大量现成的 Hook 函数(VueUse 库),接下来,我们将结合实际业务,完成我们自己的 Hook 函数!

场景分析

首先定义一个表格:

<template>
    <el-table :data="tableData" style="width: 100%">
        <el-table-column prop="date" label="Date" width="180" />
        <el-table-column prop="name" label="Name" width="180" />
        <el-table-column prop="address" label="Address" />
    </el-table> <button @click="refresh">refresh</button>
</template>

表格的数据通过 api 获取(一般写法):

< script lang = "ts"
setup >
    import {
   
        onMounted,
        ref
    } from "vue";
import {
   
    getTableDataApi
} from "./api.ts";
const tableData = ref([]);
const refresh = async () => {
   
    const data = await getTableDataApi();
    tableData.value = data;
}
onMounted(refresh); <
/script>

模拟 api:

// api.tsexport 
const getTableDataApi = () => {
   
    const data = [{
   
        date: '2016-05-03',
        name: 'Tom',
        address: 'No. 189, Grove St, Los Angeles',
    }, {
   
        date: '2016-05-02',
        name: 'Tom',
        address: 'No. 189, Grove St, Los Angeles',
    }, {
   
        date: '2016-05-04',
        name: 'Tom',
        address: 'No. 189, Grove St, Los Angeles',
    }, {
   
        date: '2016-05-01',
        name: 'Tom',
        address: 'No. 189, Grove St, Los Angeles',
    }, ]
    return new Promise(resolve => {
   
        setTimeout(() => {
   
            resolve(data)
        }, 100);
    })
}

如果存在多个表格,我们的 js 代码会变得比较复杂:

< script lang = "ts"
setup >
    import {
   
        onMounted,
        ref
    } from "vue";
import {
   
    getTableDataApi1,
    getTableDataApi2,
    getTableDataApi3
} from "./api.ts";
const tableData1 = ref([]);
const refresh1 = async () => {
   
    const data = await getTableDataApi1();
    tableData1.value = data;
}
const tableData2 = ref([]);
const refresh2 = async () => {
   
    const data = await getTableDataApi2();
    tableData2.value = data;
}
const tableData3 = ref([]);
const refresh3 = async () => {
   
    const data = await getTableDataApi3();
    tableData3.value = data;
}
onMounted(refresh1); < /script>

封装实例

封装我们的 useTable:

// useTable.ts
import {
   
    ref
} from 'vue'
export function useTable(api) {
   
    const data = ref([])
    const refresh = () => {
   
        api().then(res => data.value = res)
    };
    refresh()
    return [data, refresh]
}

改造代

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值