vue3封装Hooks实现更好的逻辑复用

vue3封装Hooks实现更好的逻辑复用

在构建前端应用中,经常会需要复用公共任务的逻辑。例如在一个管理后台中会存在很多的表格,其中的增删查改的内容就是可复用的部分,我们在写页面的过程中如果不封装的话会发现我们经常复制/粘贴。

在Vue3的Composition API中引入了Hooks的概念,为我们提供了更灵活和可组合的代码组织方式。其中的一个重要概念就是Hooks。

Hooks是一种函数,以use开头,用于封装一些可复用的逻辑。它们提供了一种在函数组件中复用状态逻辑的方式,使我们能够更好地组织组件代码,将相关的逻辑聚合在一起,实现更高水平的可维护性。

Vue3官网的案例分析

在vue3的官网中为我们介绍了一个案例,我们先分析这个案例后再实战自己封装一个useTable的逻辑。

引入官网的鼠标跟踪案例:

<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>

在上述代码中实现了对鼠标位置实时跟踪的功能。我们发现案例中的x、y的状态在鼠标移动时发生变化。不同于封装一个时间格式化的函数,我们只需要传入时间和传出时间,其中的时间是无状态的。因此我们要如何封装这样的公共逻辑呢?

我们将上面的逻辑以一个组合式函数的形式提取到外部文件中:

// 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 }
}

在组件中的使用方式:

<script setup>
import { useMouse } from './mouse.js'

const { x, y } = useMouse()
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

我们可以看到,上面封装的函数中向外暴露了我们组件中需要使用的返回值x和y,并且在组件中是可以实现响应式变化的。

上述案例大概让你明白了为什么要封装Hooks函数和如何封装和使用一个Hooks函数。

接下来我们分析一下怎么将前端项目中的Table使用逻辑封装成一个useTable的组合式函数。

封装useBorder Hooks

因为项目中使用了dataV组件,当页面宽高发生变化时组件的宽高总是无法自适应,官网给出了一个解决方案:

实现代码如下:

<BorderBox8 class="borderbox8" :key="key"></BorderBox8>
...
// dataV宽高自适应的key
let key = ref(1);

// 宽高改变框图自适应
function initBorder() {
	window.addEventListener('resize', function () {
		nextTick(() => {
			key.value++;
		});
	});
}
initBorder();

我们在多个页面都使用了dataV的组件,因此我们在多个地方都需要上述代码来实现dataV的宽高自适应。因此就想重用这段代码。在页面发生resize后key值是在发生变化的,因此我们可以用组合式函数来实现复用:

/*
 * @Description: 公用的Datav初始化逻辑,在使用dataV组件时使用
 */
import { ref, onMounted, nextTick } from 'vue';

export function useBorder() {
	const key = ref<number>(1);

	function initBorder() {
		nextTick(() => {
			console.log('resize', key.value);
			key.value++;
		});
	}
	onMounted(() => {
		window.addEventListener('resize', initBorder);
	});
	return { key };
}

在页面中使用:

import { useBorder } from '@/hooks/useBorder';
const { key } = useBorder();

封装useTable Hooks

以下案例用我目前在开发的项目作为示例,采用Typescript进行函数封装。

需要提供的功能:请求表格数据,传递分页信息

useTable中我们要实现请求接口数据,并暴露请求后的数据。因此我们需要向里面传递请求api,通过在不同的地方传入不同的请求api来实现请求不同的数据。

export interface ListResponse<R extends object> {
	code: number;
	data: {
		list: R[];
	};
}
/*
 * @Description: 公用的table逻辑,在使用表格时使用
 */
import { HttpResponse } from '@/utils/request';
import { Paging, ListResponse } from '@/api/types';
import { computed, reactive, toRefs } from 'vue';


interface TableState<R extends object> {
	tableData: R[];
	loading: boolean;
}
interface OtherSearchParams {
	[key: string]: unknown;
}
interface TableBaseMethodsType<R extends object> {
	requestApi: (
		page: number,
		keyword?: string
	) => Promise<HttpResponse<ListResponse<R>>>;
	otherSearchParams?: OtherSearchParams;
}
// table的常见数据请求和分页交互
// 接收:表格数据的范型T, 请求方法以及参数
// 抛出以下:
export default function useTable<R extends object>(
	config: TableBaseMethodsType<R>
) {
	const { otherSearchParams = {}, requestApi } = config;
	const tableState = reactive<TableState<R>>({
		loading: false,
		tableData: []
	});
	// 分页参数
	const pageParams = reactive<Paging>({
		page: 1
	});
	// 分页器配置项
	const pagination = computed(() => {
		return {
			total: tableState.tableData.length,
			curruntPage: pageParams.page
		};
	});
	/**
	 * @description: 请求加载数据
	 * @return {*}
	 */
	const loadList = async () => {
		const page = pageParams.page;
		const params = { ...otherSearchParams };
		// console.log(params, 'params');
		tableState.loading = true;
		try {
			const res = await requestApi(page, params.keyword);
			console.log(res);
			if (res.status === 200) {
				console.log(res.data.data.list, 'data');
				tableState.tableData = res.data.data.list;
				console.log(tableState.tableData, '111');
			}
		} catch (error) {
			console.log(error);
			tableState.tableData = [];
		} finally {
			tableState.loading = false;
		}
	};
	return {
		...toRefs(tableState),
		...toRefs(pageParams),
		pagination,
		loadList
	};
}

h (error) {
console.log(error);
tableState.tableData = [];
} finally {
tableState.loading = false;
}
};
return {
…toRefs(tableState),
…toRefs(pageParams),
pagination,
loadList
};
}


向外暴露请求数据,分页项。基本就实现了我们想要的逻辑了。
### Vue 3 Hooks 封装逻辑 #### 封装 Hook 的优势 在 Vue 3 中,组合式 API 提供了一种更灵活的方式来组织和重用逻辑。通过封装 hook 可以实现优雅高效的状态逻辑复用[^2]。 #### 创建自定义 Hook 函数 为了创建一个可复用的 hook,可以按照如下模式编写: ```javascript // useCustomHook.js import { ref, computed } from &#39;vue&#39; export function useCounter(initialValue = 0) { const count = ref(initialValue) const increment = () => (count.value += 1) const decrement = () => (count.value -= 1) return { count, increment, decrement } } ``` 此函数返回了一个对象,其中包含了响应式的 `count` 和两个修改器方法 `increment` 和 `decrement`。 #### 使用自定义 Hook 要在组件内部调用这个 hook,则只需导入并执行该函数即可: ```javascript <template> <div> Count: {{ counter.count }} <button @click="counter.increment">+</button> <button @click="counter.decrement">-</button> </div> </template> <script setup> import { useCounter } from &#39;./useCustomHook&#39; const counter = useCounter() </script> ``` 这种方式不仅使代码更加模块化,而且提高了不同组件间的逻辑共享能力。 #### 常见面试问题及解答 - **为什么选择 Composition API 而不是 Options API?** Composition API 更加直观地表达了业务逻辑之间的关系,并允许开发者更好地组织复杂的应用程序结构[^1]。 - **如何解决多个钩子之间的依赖关系?** 可以通过参数传递的方式让某些 hook 成为其他 hook 的输入源;也可以利用 provide/inject 或者 Vuex/Pinia 来管理全局状态[^3]。 - **怎样测试基于 Composition API 编写的组件?** 由于每个 hook 都是一个独立的功能单元,因此可以直接对其进行单元测试而不必涉及整个组件实例。对于集成测试来说,仍然遵循标准流程渲染组件并验证其行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值