溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue3如何寫列表頁(yè)讓性能更好

發(fā)布時(shí)間:2022-12-27 10:29:47 來(lái)源:億速云 閱讀:176 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“Vue3如何寫列表頁(yè)讓性能更好”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Vue3如何寫列表頁(yè)讓性能更好”吧!

在開(kāi)發(fā)管理后臺(tái)過(guò)程中,一定會(huì)遇到不少了增刪改查頁(yè)面,而這些頁(yè)面的邏輯大多都是相同的,如獲取列表數(shù)據(jù),分頁(yè),篩選功能這些基本功能。而不同的是呈現(xiàn)出來(lái)的數(shù)據(jù)項(xiàng)。還有一些操作按鈕。

Vue3如何寫列表頁(yè)讓性能更好

對(duì)于剛開(kāi)始只有 1,2 個(gè)頁(yè)面的時(shí)候大多數(shù)開(kāi)發(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)的主要原因之一

前置知識(shí)

  • Vue

  • Vue 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);
}

如何獲取列表數(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<ItemType extends Object>(
  listRequestFn: Function
) {
  // 忽略其他代碼
  const list = ref<ItemType[]>([]);
}

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

  • 執(zhí)行流程

  1. 設(shè)置加載狀態(tài)

  2. 調(diào)用外部傳入的函數(shù),將獲取到的數(shù)據(jù)賦值到listtotal

  3. 關(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<ItemType extends Object>(
  listRequestFn: Function
) {
  // 忽略其他代碼
  // 數(shù)據(jù)
  const list = ref<ItemType[]>([]);
  // 過(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;
    }
  };
}

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

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

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

現(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<Object>) {
  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;
    }
  };
}

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

清空篩選器字段

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

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

什么是 Reflect?看看這一篇文章Reflect 映射對(duì)象

export default function useList<
  ItemType extends Object,
  FilterOption extends Object
>(listRequestFn: Function, filterOption: Ref<Object>) {
  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();
  };
}

導(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<Object>,
  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");
    }
  };
}

注意,傳入的 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<Object>,
  exportRequestFn?: Function,
  options? :OptionsType
) {
  // ...
}

設(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<Object>,
  exportRequestFn?: Function,
  options = DEFAULT_OPTIONS
) {
  // ...
}

在沒(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" });
}

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

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?.();
  }
};

useList 使用方法

<template>
  <el-collapse>
    <el-collapse-item title="篩選條件" name="1">
      <el-form label-position="left" label-width="90px" :model="filterOption">
        <el-row :gutter="20">
          <el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
            <el-form-item label="用戶名">
              <el-input
                v-model="filterOption.name"
                placeholder="篩選指定簽名名稱"
              />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
            <el-form-item label="注冊(cè)時(shí)間">
              <el-date-picker
                v-model="filterOption.timeRange"
                type="daterange"
                unlink-panels
                range-separator="到"
                start-placeholder="開(kāi)始時(shí)間"
                end-placeholder="結(jié)束時(shí)間"
                format="YYYY-MM-DD HH:mm"
                value-format="YYYY-MM-DD HH:mm"
              />
            </el-form-item>
          </el-col>
          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
            <el-row class="flex mt-4">
              <el-button type="primary" @click="filter">篩選</el-button>
              <el-button type="primary" @click="reset">重置</el-button>
            </el-row>
          </el-col>
        </el-row>
      </el-form>
    </el-collapse-item>
  </el-collapse>
  <el-table v-loading="loading" :data="list" border style="width: 100%">
    <el-table-column label="用戶名" min-width="110px">
      <template #default="scope">
        {{ scope.row.name }}
      </template>
    </el-table-column>
    <el-table-column label="手機(jī)號(hào)碼" min-width="130px">
      <template #default="scope">
        {{ scope.row.mobile || "未綁定手機(jī)號(hào)碼" }}
      </template>
    </el-table-column>
    <el-table-column label="郵箱地址" min-width="130px">
      <template #default="scope">
        {{ scope.row.email || "未綁定郵箱地址" }}
      </template>
    </el-table-column>
    <el-table-column prop="createAt" label="注冊(cè)時(shí)間" min-width="220px" />
    <el-table-column width="200px" fixed="right" label="操作">
      <template #default="scope">
        <el-button type="primary" link @click="detail(scope.row)"
          >詳情</el-button
        >
      </template>
    </el-table-column>
  </el-table>
  <div v-if="total > 0" class="flex justify-end mt-4">
    <el-pagination
      v-model:current-page="curPage"
      v-model:page-size="pageSize"
      background
      layout="sizes, prev, pager, next"
      :total="total"
      :page-sizes="[10, 30, 50]"
    />
  </div>
</template>
<script setup>
import { UserInfoApi } from "@/network/api/User";
import useList from "@/lib/hooks/useList/index";
const filterOption = ref<UserInfoApi.FilterOptionType>({});
const {
  list,
  loading,
  reset,
  filter,
  curPage,
  pageSize,
  reload,
  total,
  loadData,
} = useList<UserInfoApi.UserInfo[], UserInfoApi.FilterOptionType>(
  UserInfoApi.list,
  filterOption
);
</script>

感謝各位的閱讀,以上就是“Vue3如何寫列表頁(yè)讓性能更好”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Vue3如何寫列表頁(yè)讓性能更好這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI