vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > vue3封装Hooks实现更好的逻辑复用

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

作者:sulli_la

别再复制粘贴表格逻辑了!学会Vue3的useTable Hooks,轻松复用增删查改和分页功能,本文从鼠标跟踪案例入手,手把手教你封装组合式函数,解决DataV组件自适应问题,并用TypeScript实现可复用的表格数据请求逻辑,提升代码可维护性

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

在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
};
}

向外暴露请求数据,分页项。基本就实现了我们想要的逻辑了。

​总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文