天天速遞!在Vue3這樣子寫頁(yè)面更快更高效

2023-01-05 18:15:21 來(lái)源:51CTO博客

前言

在開發(fā)管理的后臺(tái)過(guò)程中,會(huì)增加、刪除、更改很多頁(yè)面,這些頁(yè)面的邏輯大多相同,比如獲取列表數(shù)據(jù)、分頁(yè)、篩選等基本功能。不同的是呈現(xiàn)的數(shù)據(jù)項(xiàng)。還有一些操作按鈕。

對(duì)于剛開始只有 1,2 個(gè)頁(yè)面的時(shí)候大多數(shù)開發(fā)者可能會(huì)直接將之前的頁(yè)面代碼再拷貝多一份出來(lái),而隨著項(xiàng)目的推進(jìn)類似頁(yè)面數(shù)量可能會(huì)越來(lái)越多,這直接導(dǎo)致項(xiàng)目代碼耦合度越來(lái)越高。


(資料圖片)

這也是為什么在項(xiàng)目中一些可復(fù)用的函數(shù)或組件要抽離出來(lái)的主要原因之一

下面,我們封裝一個(gè)通用的??useList??,適配大多數(shù)增刪改查的列表頁(yè)面,讓你更快更高效的完成任務(wù),準(zhǔn)點(diǎn)下班 ~

前置知識(shí)

VueVue Composition Api

封裝

我們需要將一些通用的參數(shù)和函數(shù)抽離出來(lái),封裝成一個(gè)通用??hook??,后續(xù)在其他頁(yè)面復(fù)用相同功能更加簡(jiǎn)單方便。

定義列表頁(yè)面必不可少的分頁(yè)數(shù)據(jù)

export default function useList() {  // 加載態(tài)  const loading = ref(false);  // 當(dāng)前頁(yè)  const curPage = ref(1);  // 總數(shù)量  const total = ref(0);  // 分頁(yè)大小  const pageSize = ref(10);}復(fù)制代碼

如何獲取列表數(shù)據(jù)

思考一番,讓??useList??函數(shù)接收一個(gè)??listRequestFn??參數(shù),用于請(qǐng)求列表中的數(shù)據(jù)。

定義一個(gè)??list??變量,用于存放網(wǎng)絡(luò)請(qǐng)求回來(lái)的數(shù)據(jù)內(nèi)容,由于在內(nèi)部無(wú)法直接確定列表數(shù)據(jù)類型,通過(guò)泛型的方式讓外部提供列表數(shù)據(jù)類型。

export default function useList(  listRequestFn: Function) {  // 忽略其他代碼  const list = ref([]);}復(fù)制代碼

在??useList??中創(chuàng)建一個(gè)??loadData??函數(shù),用于調(diào)用獲取數(shù)據(jù)函數(shù),該函數(shù)接收一個(gè)參數(shù)用于獲取指定頁(yè)數(shù)的數(shù)據(jù)(可選,默認(rèn)為??curPage??的值)。

執(zhí)行流程設(shè)置加載狀態(tài)調(diào)用外部傳入的函數(shù),將獲取到的數(shù)據(jù)賦值到??list??和??total??中關(guān)閉加載態(tài)

這里使用了 async/await 語(yǔ)法,假設(shè)請(qǐng)求出錯(cuò)、解構(gòu)出錯(cuò)情況會(huì)走 catch 代碼塊,再關(guān)閉加載態(tài)

這里需要注意,傳入的 listRequestFn 函數(shù)接收的參數(shù)數(shù)量和類型是否正常對(duì)應(yīng)上請(qǐng)根據(jù)實(shí)際情況進(jìn)行調(diào)整

export default function useList(  listRequestFn: Function) {  // 忽略其他代碼  // 數(shù)據(jù)  const list = ref([]);  // 過(guò)濾數(shù)據(jù)  // 獲取列表數(shù)據(jù)  const loadData = async (page = curPage.value) => {    // 設(shè)置加載中    loading.value = true;    try {      const {        data,        meta: { total: count },      } = await listRequestFn(pageSize.value, page);      list.value = data;      total.value = count;    } catch (error) {      console.log("請(qǐng)求出錯(cuò)了", "error");    } finally {      // 關(guān)閉加載中      loading.value = false;    }  };}復(fù)制代碼

別忘了,還有切換分頁(yè)要處理

使用 ??watch?? 函數(shù)監(jiān)聽數(shù)據(jù),當(dāng)??curPage??,??pageSize??的值發(fā)生改變時(shí)調(diào)用??loadData??函數(shù)獲取新的數(shù)據(jù)。

export default function useList(  listRequestFn: Function) {  // 忽略其他代碼  // 監(jiān)聽分頁(yè)數(shù)據(jù)改變  watch([curPage, pageSize], () => {    loadData(curPage.value);  });}復(fù)制代碼

現(xiàn)在實(shí)現(xiàn)了基本的列表數(shù)據(jù)獲取

實(shí)現(xiàn)數(shù)據(jù)篩選器

在龐大的數(shù)據(jù)列表中,數(shù)據(jù)篩選是必不可少的功能

通常,我會(huì)將篩選條件字段定義在一個(gè)??ref??中,在請(qǐng)求時(shí)將??ref??丟到請(qǐng)求函數(shù)即可。

在 useList 函數(shù)中,第二個(gè)參數(shù)接收一個(gè)??filterOption??對(duì)象,對(duì)應(yīng)列表中的篩選條件字段。

調(diào)整一下??loadData??函數(shù),在請(qǐng)求函數(shù)中傳入??filterOption??對(duì)象即可

注意,傳入的 listRequestFn 函數(shù)接收的參數(shù)數(shù)量和類型是否正常對(duì)應(yīng)上請(qǐng)根據(jù)實(shí)際情況進(jìn)行調(diào)整

export default function useList<  ItemType extends Object,  FilterOption extends Object>(listRequestFn: Function, filterOption: Ref) {  const loadData = async (page = curPage.value) => {    // 設(shè)置加載中    loading.value = true;    try {      const {        data,        meta: { total: count },      } = await listRequestFn(pageSize.value, page, filterOption.value);      list.value = data;      total.value = count;    } catch (error) {      console.log("請(qǐng)求出錯(cuò)了", "error");    } finally {      // 關(guān)閉加載中      loading.value = false;    }  };}復(fù)制代碼

注意,這里 filterOption 參數(shù)類型需要的是 ref 類型,否則會(huì)丟失響應(yīng)式 無(wú)法正常工作

清空篩選器字段

在頁(yè)面中,有一個(gè)重置的按鈕,用于清空篩選條件。這個(gè)重復(fù)的動(dòng)作可以交給 reset 函數(shù)處理。

通過(guò)使用 Reflect 將所有值設(shè)定為??undefined??,再重新請(qǐng)求一次數(shù)據(jù)。

export default function useList<  ItemType extends Object,  FilterOption extends Object>(listRequestFn: Function, filterOption: Ref) {  const reset = () => {    if (!filterOption.value) return;    const keys = Reflect.ownKeys(filterOption.value);    filterOption.value = {} as FilterOption;    keys.forEach((key) => {      Reflect.set(filterOption.value!, key, undefined);    });    loadData();  };}復(fù)制代碼

導(dǎo)出功能

除了對(duì)數(shù)據(jù)的查看,有些界面還需要有導(dǎo)出數(shù)據(jù)功能(例如導(dǎo)出 csv,excel 文件),我們也把導(dǎo)出功能寫到??useList??里

通常,導(dǎo)出功能是調(diào)用后端提供的導(dǎo)出??Api??獲取一個(gè)文件下載地址,和??loadData??函數(shù)類似,從外部獲取??exportRequestFn??函數(shù)來(lái)調(diào)用??Api??

在函數(shù)中,新增一個(gè)??exportFile??函數(shù)調(diào)用它。

export default function useList<  ItemType extends Object,  FilterOption extends Object>(  listRequestFn: Function,  filterOption: Ref,  exportRequestFn?: Function) {  // 忽略其他代碼  const exportFile = async () => {    if (!exportRequestFn) {      throw new Error("當(dāng)前沒(méi)有提供exportRequestFn函數(shù)");    }    if (typeof exportRequestFn !== "function") {      throw new Error("exportRequestFn必須是一個(gè)函數(shù)");    }    try {      const {        data: { link },      } = await exportRequestFn(filterOption.value);      window.open(link);    } catch (error) {      console.log("導(dǎo)出失敗", "error");    }  };}復(fù)制代碼

注意,傳入的 exportRequestFn 函數(shù)接收的參數(shù)數(shù)量和類型是否正常對(duì)應(yīng)上請(qǐng)根據(jù)實(shí)際情況進(jìn)行調(diào)整

優(yōu)化

現(xiàn)在,整個(gè)??useList??已經(jīng)滿足了頁(yè)面上的需求了,擁有了獲取數(shù)據(jù),篩選數(shù)據(jù),導(dǎo)出數(shù)據(jù),分頁(yè)功能

還有一些細(xì)節(jié)方面,在上面所有代碼中的??try..catch??中的??catch??代碼片段并沒(méi)有做任何的處理,只是簡(jiǎn)單的??console.log??一下

提供鉤子

在??useList??新增一個(gè) Options 對(duì)象參數(shù),用于函數(shù)成功、失敗時(shí)執(zhí)行指定鉤子函數(shù)與輸出消息內(nèi)容。

定義 Options 類型

export interface MessageType {  GET_DATA_IF_FAILED?: string;  GET_DATA_IF_SUCCEED?: string;  EXPORT_DATA_IF_FAILED?: string;  EXPORT_DATA_IF_SUCCEED?: string;}export interface OptionsType {  requestError?: () => void;  requestSuccess?: () => void;  message: MessageType;}export default function useList<  ItemType extends Object,  FilterOption extends Object>(  listRequestFn: Function,  filterOption: Ref,  exportRequestFn?: Function,  options? :OptionsType) {  // ...}復(fù)制代碼

設(shè)置??Options??默認(rèn)值

const DEFAULT_MESSAGE = {  GET_DATA_IF_FAILED: "獲取列表數(shù)據(jù)失敗",  EXPORT_DATA_IF_FAILED: "導(dǎo)出數(shù)據(jù)失敗",};const DEFAULT_OPTIONS: OptionsType = {  message: DEFAULT_MESSAGE,};export default function useList<  ItemType extends Object,  FilterOption extends Object>(  listRequestFn: Function,  filterOption: Ref,  exportRequestFn?: Function,  options = DEFAULT_OPTIONS) {  // ...}復(fù)制代碼

在沒(méi)有傳遞鉤子的情況霞,推薦設(shè)置默認(rèn)的失敗時(shí)信息顯示

優(yōu)化??loadData??,??exportFile??函數(shù)

基于 elementui 封裝 message 方法

import { ElMessage, MessageOptions } from "element-plus";export function message(message: string, option?: MessageOptions) {  ElMessage({ message, ...option });}export function warningMessage(message: string, option?: MessageOptions) {  ElMessage({ message, ...option, type: "warning" });}export function errorMessage(message: string, option?: MessageOptions) {  ElMessage({ message, ...option, type: "error" });}export function infoMessage(message: string, option?: MessageOptions) {  ElMessage({ message, ...option, type: "info" });}復(fù)制代碼

loadData 函數(shù)

const loadData = async (page = curPage.value) => {  loading.value = true;  try {    const {      data,      meta: { total: count },    } = await listRequestFn(pageSize.value, page, filterOption.value);    list.value = data;    total.value = count;    // 執(zhí)行成功鉤子    options?.message?.GET_DATA_IF_SUCCEED &&      message(options.message.GET_DATA_IF_SUCCEED);    options?.requestSuccess?.();  } catch (error) {    options?.message?.GET_DATA_IF_FAILED &&      errorMessage(options.message.GET_DATA_IF_FAILED);    // 執(zhí)行失敗鉤子    options?.requestError?.();  } finally {    loading.value = false;  }};復(fù)制代碼

exportFile 函數(shù)

const exportFile = async () => {  if (!exportRequestFn) {    throw new Error("當(dāng)前沒(méi)有提供exportRequestFn函數(shù)");  }  if (typeof exportRequestFn !== "function") {    throw new Error("exportRequestFn必須是一個(gè)函數(shù)");  }  try {    const {      data: { link },    } = await exportRequestFn(filterOption.value);    window.open(link);    // 顯示信息    options?.message?.EXPORT_DATA_IF_SUCCEED &&      message(options.message.EXPORT_DATA_IF_SUCCEED);    // 執(zhí)行成功鉤子    options?.exportSuccess?.();  } catch (error) {    // 顯示信息    options?.message?.EXPORT_DATA_IF_FAILED &&      errorMessage(options.message.EXPORT_DATA_IF_FAILED);    // 執(zhí)行失敗鉤子    options?.exportError?.();  }};復(fù)制代碼

useList 使用方法

<script setup lang="ts">import { UserInfoApi } from "@/network/api/User";import useList from "@/lib/hooks/useList/index";const filterOption = ref({});const {  list,  loading,  reset,  filter,  curPage,  pageSize,  reload,  total,  loadData,} = useList(  UserInfoApi.list,  filterOption);</script>復(fù)制代碼

附件:??點(diǎn)此下載??

標(biāo)簽: 列表數(shù)據(jù) 是否正常 注冊(cè)時(shí)間

上一篇:優(yōu)維低代碼:I18n 國(guó)際化
下一篇:熱門:達(dá)夢(mèng)數(shù)據(jù)庫(kù)DM8安裝準(zhǔn)備及注意事項(xiàng)