DEV Community

山田
山田

Posted on • Originally published at juejin.cn

How to write a list page faster

preface

In the background process of development and management, there must be many pages to add, delete, change and query, and most of the logic of these pages are the same, such as the basic functions of obtaining list data, paging, and filtering. The difference is the data items presented. There are also some operation buttons.

20221203192923

When there are only 1 or 2 pages at the beginning, most developers may directly copy another copy of the previous page code. With the progress of the project, the number of similar pages may become more and more, which directly leads to higher and higher coupling of project code.

This is one of the main reasons why some reusable functions or components in the project should be separated

Next, we encapsulate a general useList, which is adapted to most of the list pages for addition, deletion, modification and query, so that you can complete tasks faster and more efficiently and leave on time ~

20221203195440

Antecedent knowledge

Coding

We need to extract some common parameters and functions and encapsulate them into a common hook. It is easier to reuse the same functions in other pages later.

Define the paging data necessary for a list page

export default function useList() {
  // isLoading
  const loading = ref(false);
  // current page number
  const curPage = ref(1);
  // list total
  const total = ref(0);
  // current page size
  const pageSize = ref(10);
}
Enter fullscreen mode Exit fullscreen mode

How to get List Data

Think about it. Let the useList function receive a listRequestFn parameter for requesting data in the list.

Define a list variable to store the data content returned from the network request. Since the list data type cannot be directly determined internally, the list data type can be provided externally in a generic way.

export default function useList<ItemType extends Object>(
  listRequestFn: Function
) {
  // ignore other code
  const list = ref<ItemType[]>([]);
}
Enter fullscreen mode Exit fullscreen mode

Create a loadData function in useList to call the data acquisition function. This function receives a parameter to obtain the data of the specified number of pages (optional, default to the value of curPage).

  • Execution process
  1. Set the loading status
  2. Call the external incoming function to assign the acquired data to list and total
  3. Close the loading state

The async/await syntax is used here. Assume that a request error or a deconstruction error occurs, the catch code block will be followed, and then the loading state will be closed

Note that whether the number and type of parameters received by the incoming listRequestFn function correspond normally

Please adjust according to the actual situation

export default function useList<ItemType extends Object>(
  listRequestFn: Function
) {
  // ignore other code
  // data list
  const list = ref<ItemType[]>([]);
  // filter data
  // get data
  const loadData = async (page = curPage.value) => {
    // Open Load Status
    loading.value = true;
    try {
      const {
        data,
        meta: { total: count },
      } = await listRequestFn(pageSize.value, page);
      list.value = data;
      total.value = count;
    } catch (error) {
      console.log("request error", "error");
    } finally {
      // Close the loading state
      loading.value = false;
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Don't forget, there is also paging to be handled

Use the watch function to listen for data. When the values of curPage and pageSize change, call the loadData function to obtain new data.

export default function useList<ItemType extends Object>(
  listRequestFn: Function
) {
  // ignore other code
  // watch page data
  watch([curPage, pageSize], () => {
    loadData(curPage.value);
  });
}
Enter fullscreen mode Exit fullscreen mode

Now the basic list data acquisition is realized

Implement Data Filters

In a huge data list, data filtering is an essential function

Generally, I define the filter criteria field in a ref, and throw the ref to the request function when requesting.

In the useList function, the second parameter receives a filterOption object, which corresponds to the filter condition field in the list.

Adjust the loadData function and pass in the filterOption object to the request function

Note whether the number and type of parameters received by the incoming listRequestFn function correspond normally

Please adjust according to the actual situation

export default function useList<
  ItemType extends Object,
  FilterOption extends Object
>(listRequestFn: Function, filterOption: Ref<Object>) {
  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;
    } catch (error) {
      console.log("request error", "error");
    } finally {
      // Close the loading state
      loading.value = false;
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Note that the filterOption parameter type requires a ref type, otherwise the responders will be lost and will not work properly

Clear Filter Fields

In the page, there is a reset button to clear the filter conditions. This repeated action can be handled by the reset function.

Use Reflect to set all values to undefined and request data again.

What is Reflect? Take a look at this article Reflect Mapping Object

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();
  };
}
Enter fullscreen mode Exit fullscreen mode

export function

In addition to viewing data, some interfaces also need to have the function of exporting data (such as exporting csv and excel files). We also write the export function to useList

In general, the export function calls the export Api provided by the backend to obtain a file download address. Similar to the loadData function, the exportRequestFn function is obtained externally to call the 'Api`

In the function, add an exportFile function to call it.

`typescript
export default function useList<
ItemType extends Object,
FilterOption extends Object

(
listRequestFn: Function,
filterOption: Ref,
exportRequestFn?: Function
) {
// ignore other code
const exportFile = async () => {
if (!exportRequestFn) {
throw new Error("Please provide exportRequestFn function");
}
if (typeof exportRequestFn !== "function") {
throw new Error("exportRequestFn must be a function);
}
try {
const {
data: { link },
} = await exportRequestFn(filterOption.value);
window.open(link);
} catch (error) {
console.log("export error", "error");
}
};
}
`

Note that whether the number and type of parameters received by the imported exportRequestFn function correspond normally
Please adjust according to the actual situation

Optimization

Now, the entire useList has met the needs of the page, with the functions of obtaining data, filtering data, exporting data, and paging

There are also some details. The catch code fragments in the try.. Catch in all the above codes have not been processed at all, just a simple console. log

Provide hook

In useList, add an Options object parameter, which is used to execute the specified hook function and output message content when the function succeeds or fails.

Define Options Type

`typescript
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
) {
// ...
}
`

Set Options default value

`typescript
const DEFAULT_MESSAGE = {
GET_DATA_IF_FAILED: "Failed to get list data",
EXPORT_DATA_IF_FAILED: "Failed to export data",
};

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
) {
// ...
}
`

It is recommended to set the default failure message when there is no transfer hook

Optimize loadData, exportFile functions

Encapsulating message method based on element ui

`typescript
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 function

typescript
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;
// execute succeed function
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);
// execute failed function
options?.requestError?.();
} finally {
loading.value = false;
}
};

exportFile function

typescript
const exportFile = async () => {
if (!exportRequestFn) {
throw new Error("Please provide exportRequestFn function");
}
if (typeof exportRequestFn !== "function") {
throw new Error("ExportRequestFn must be a function");
}
try {
const {
data: { link },
} = await exportRequestFn(filterOption.value);
window.open(link);
// show message
options?.message?.EXPORT_DATA_IF_SUCCEED &&
message(options.message.EXPORT_DATA_IF_SUCCEED);
// execute succeed function
options?.exportSuccess?.();
} catch (error) {
// show message
options?.message?.EXPORT_DATA_IF_FAILED &&
errorMessage(options.message.EXPORT_DATA_IF_FAILED);
// execute failed function
options?.exportError?.();
}
};

Usage of useList

`vue







v-model="filterOption.name"
placeholder="筛选指定签名名称"
/>




v-model="filterOption.timeRange"
type="daterange"
unlink-panels
range-separator="到"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
/>




筛选
重置









{{ scope.row.name }}




{{ scope.row.mobile || "未绑定手机号码" }}




{{ scope.row.email || "未绑定邮箱地址" }}





>详情 >



v-model:page-size="pageSize"
background
layout="sizes, prev, pager, next"
:total="total"
:page-sizes="[10, 30, 50]"
/>

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

`

The complete code of the useList in this article is shown in
https://github.com/QC2168/snippets/tree/main/useList

💡 If you have better suggestions for this hook, welcome topror leave a message in the comment area

In addition, in order to save time in searching for encapsulated code fragments and improve work efficiency in daily development 🐟 Time++), this warehouse
It also stores some code fragments encapsulated by the third party ✨, Convenient for everyone 😄😄 (Continuously updating~~)

Top comments (0)