Hooks或者组合式函数,可用于订阅变化的数据源、事件源并封装成可被复用的逻辑。封装的关键在于封装不变的部分,暴露变化的部分给用户。
在常见的分页这一逻辑中,查询参数、分页参数、列表数据及分页、查询、重置等方法都需慎重考虑哪部分应该被封装在组合式函数内,哪部分应该留给用户,下方就是一个对此业务的组合式函数的用法和实现:
用法:
<template>
<el-input clearable v-model='query.goodsName'></el-input>
<el-button type="primary" @click="search"> 查询 </el-button>
<el-button @click="reset">重置</el-button>
<el-table :data="list">
<!-- ... -->
</el-table>
<el-pagination
v-if="list?.length"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:size="meta.size"
:total="meta.total"
:current-page="meta.current"
:pageSizes="meta.sizes"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
</template>
<script>
import usePaging from '@/hooks/usePaging'
const query = ref({ goodsName: '' }) // 查询参数
// 获取列表数据函数
const fetchList = async (params: any) => {
try {
const res = await getGoodsListApi(params)
return res?.data
} catch (error) {
console.error('fetchList-error', error)
return null
}
}
// 使用分页组合式函数,获取列表数据及分页信息
const { meta, list, search, getData, reset, handleSizeChange, handleCurrentChange } =
usePaging(fetchList, query)
</script>
实现:
ts版本
import { ref, reactive, onMounted, type Ref } from 'vue'
// --- Types start ----
// 定义获取列表数据的参数接口
interface FetchListParams {
condition: Record<string, any>
current: number
size: number
}
// 定义获取列表数据的函数类型
type FetchListFunction = (params: FetchListParams) => Promise<any>
// 定义分页元数据接口
interface Meta {
current: number
size: number
total: number
sizes: number[]
}
// 定义额外选项的接口,包含初始查询数据、是否在mounted时调用getData以及初始分页配置
interface ExtraOptions {
initialQueryData?: Record<string, any>
isCalledOnMounted?: boolean
initialMeta?: Meta
}
// --- Types end ----
/**
* usePaging 组合式函数用于管理分页数据的获取和状态。
*
* @param fetchList - 用于获取列表数据的异步函数。
* @param query - 包含查询条件的响应式引用对象。
* @param extraOptions - 包含额外配置的可选对象,包括初始查询条件、是否在组件挂载时获取数据以及初始分页配置。
* @returns 分页和列表管理的一系列方法和状态。
*/
export default function usePaging(
fetchList: FetchListFunction,
query: Ref<Record<string, any>>,
extraOptions?: ExtraOptions
) {
// 设置 extraOptions 的默认值
const options = {
initialQueryData: {},
isCalledOnMounted: true,
initialMeta: {
current: 1,
size: 10,
total: 0,
sizes: [10, 15, 20, 30, 40],
},
...extraOptions,
}
const list = ref([]) // 列表数据
const meta = reactive(options.initialMeta) // 使用 options 中的 initialMeta 作为初始分页配置
const initialQuery = { ...options.initialQueryData }
onMounted(() => {
if (options.isCalledOnMounted) getData()
})
/**
* 获取数据
*/
const getData = async () => {
try {
const result = await fetchList({
condition: filterData(query.value),
current: meta.current,
size: meta.size,
})
if (result) {
meta.total = result.total
list.value = result.records
}
} catch (error) {
console.error('getData-error', error)
}
}
/**
* 过滤数据:过滤空值参数、并将 * 转换为 %
* @param data
* @returns
*/
const filterData = (data: Record<string, any>) => {
const temp = Object.entries(data).reduce((result: Record<string, any>, [key, value]) => {
// 过滤值为 '' undefined null 空数组 的字段
if (
value !== null &&
value !== '' &&
value !== undefined &&
(!Array.isArray(value) || value.length > 0)
) {
result[key] = value
}
return result
}, {})
Object.entries(temp).forEach(([key, value]) => {
if (typeof value === 'string') {
temp[key] = value.replace(/*/g, '%')
}
})
return temp
}
/**
* 搜索
*/
const search = () => {
meta.current = 1
getData()
}
/**
* 重置
*/
const reset = () => {
Object.keys(query.value).forEach((key) => {
query.value[key] = initialQuery[key]
})
meta.current = 1
getData()
}
/**
* 页面数据条数变化
* @param val
*/
const handleSizeChange = (val: number | Event) => {
meta.size = typeof val === 'number' ? val : (val as CustomEvent).detail
meta.current = 1
getData()
}
/**
* 页码变化
* @param val
*/
const handleCurrentChange = (val: number | Event) => {
meta.current = typeof val === 'number' ? val : (val as CustomEvent).detail
getData()
}
return {
meta, // 分页配置
list, // 列表数据
getData, // 获取数据
search, // 搜索
reset, // 重置
handleSizeChange, // 页面条数变化处理函数
handleCurrentChange, // 页码变化函数
}
}
js版本
import { ref, reactive, onMounted } from 'vue';
/**
* usePaging 组合式函数用于管理分页数据的获取和状态。
*
* @param {Function} fetchList - 用于获取列表数据的异步函数。
* @param {Object} query - 包含查询条件的响应式引用对象。
* @param {Object} [extraOptions] - 包含额外配置的可选对象,包括初始查询条件、是否在组件挂载时获取数据以及初始分页配置。
* @returns {Object} 分页和列表管理的一系列方法和状态。
*/
export default function usePaging(fetchList, query, extraOptions) {
// 设置 extraOptions 的默认值
const options = {
initialQueryData: {},
isCalledOnMounted: true,
initialMeta: {
current: 1,
size: 10,
total: 0,
sizes: [10, 15, 20, 30, 40],
},
...extraOptions,
};
const list = ref([]); // 列表数据
const meta = reactive(options.initialMeta); // 使用 options 中的 initialMeta 作为初始分页配置
const initialQuery = { ...options.initialQueryData };
onMounted(() => {
if (options.isCalledOnMounted) getData();
});
/**
* 获取数据
*/
const getData = async () => {
try {
const result = await fetchList({
condition: filterData(query.value),
current: meta.current,
size: meta.size,
});
if (result) {
meta.total = result.total;
list.value = result.records;
}
} catch (error) {
console.error('getData-error', error);
}
};
/**
* 过滤数据:过滤空值参数、并将 * 转换为 %
* @param {Object} data
* @returns {Object}
*/
const filterData = (data) => {
const temp = Object.entries(data).reduce((result, [key, value]) => {
// 过滤值为 '' undefined null 空数组 的字段
if (value !== null && value !== '' && value !== undefined && (!Array.isArray(value) || value.length > 0)) {
result[key] = value;
}
return result;
}, {});
Object.entries(temp).forEach(([key, value]) => {
if (typeof value === 'string') {
temp[key] = value.replace(/*/g, '%');
}
});
return temp;
};
/**
* 搜索
*/
const search = () => {
meta.current = 1;
getData();
};
/**
* 重置
*/
const reset = () => {
Object.keys(query.value).forEach((key) => {
query.value[key] = initialQuery[key];
});
meta.current = 1;
getData();
};
/**
* 页面数据条数变化
* @param {number|Event} val
*/
const handleSizeChange = (val) => {
meta.size = typeof val === 'number' ? val : (val as CustomEvent).detail;
meta.current = 1;
getData();
};
/**
* 页码变化
* @param {number|Event} val
*/
const handleCurrentChange = (val) => {
meta.current = typeof val === 'number' ? val : (val as CustomEvent).detail;
getData();
};
return {
meta, // 分页配置
list, // 列表数据
getData, // 获取数据
search, // 搜索
reset, // 重置
handleSizeChange, // 页面条数变化处理函数
handleCurrentChange, // 页码变化函数
};
}
配置参数说明
usePaging(
fetchList: FetchListFunction,
query: Ref<Record<string, any>>,
extraOptions?: ExtraOptions
)
| 参数名 | 类型 | 说明 |
|---|---|---|
| fetchList | FetchListFunction | 获取列表数据的函数 |
| query | Ref<Record<string, any>> | 查询参数的ref引用 |
| extraOptions | ExtraOptions | 额外参数配置 |
| initialQueryData: Record<string, any> | 初始默认query参数对象,未传则使用初次传入的query对象 | |
| isCalledOnMounted: boolean | 是否在onMounted中调用获取数据操作,默认true | |
| initialMeta:Meta | 初始的分页参数配置,默认如下: |
{
current: 1,
size: 10,
total: 0,
sizes: [10, 15, 20, 30, 40],
}