HOOKS

Table Data useTableData for Public Hooks Encapsulation

Table Data Hook Encapsulation: useTableData

Foreword

For front-end developers who frequently build enterprise management backends, working with tables to manipulate data is essential. When refactoring existing projects, it’s a good practice to encapsulate some reusable custom hooks. This article is for ‘useTableData.js’/‘useTableData.ts’

Hooks encapsulated based on a personal project environment; this article only shares insights and best practices for hook encapsulation, so the accompanying code may not be directly applicable to others.

Project Environment

Vue 3.x + Ant Design Vue 3.x + Vite 3.x

Encapsulation and Decomposition: Declaring Variables


const loading = ref(false);        // 表格数据UI交互Loading
const tableData = shallowRef([]);  // 表格数据ShallowRef全响应式
const totalElements = ref(0);      // 总数据量totalElements

Whether you have used Ant Design Vue, Element UI or other UI FrameWork frameworks, what you need in the form must be tableData and totalElements.

Encapsulation and Decomposition: Requesting the API

The following code only explains the encapsulation idea

const getTableData = async() => {
  ***
  const { data ||
    data: { content, totalElements: total }
  } = await API(QueryParams)   // 请求接口,
  ***
  tableData.value = data ||
  data.content ||
  data.content.map(item => {***})  //解构data进行赋值
  totalElements.value = total
}

Interface request table data, one of the most basic “add, delete, modify and check”, for the back-end to provide interfaces and related code output specifications after the convention, package Hooks at the same time to consider whether the tableData can be directly assigned by the data deconstructed content, or need to be secondary processing

Package decomposition: filter query and reset query

const search = async () => {
  queryParams.value.pageIndex = 1; // 根据筛选想查询数据,重置页码为1,调用接口
  await getTableData();
};

const resetSearch = async () => {
  if (isFunction(options.resetParams)) {
    options.resetParams();       // 重置请求参数
    await getTableData();
  }
};

Encapsulation and Decomposition: Retrieving Paginator Configuration

The following packages are based on the project environment Ant Design Vue 3.x

const getPaginationOptions = () => {
  return {
    total: totalElements.value,
    current: queryParams.value.pageIndex,
    pageSize: queryParams.value.pageSize,
    showQuickJumper: true,
    showSizeChanger: true,
    showTotal: _total => `共 ${_total} 条数据`,
  };
};

Encapsulation and decomposition: table change event (paging, sorting, filtering change trigger)

The following packages are based on the project environment Ant Design Vue 3.x

const onTableChange = async (page, _, sorter) => {   // page: 页码, sorter: 排序字段
  queryParams.value.pageIndex = page.current || 1;
  queryParams.value.pageSize = page.pageSize || 10;
  if (sorter && Object.keys(sorter).length > 0) {
    if (sorter.order) {
      queryParams.value.orderBy = sorter.field;      // asc \ desc
      queryParams.value.direction = sorter.order.slice(0, -3);
    } else {
      queryParams.value.orderBy = '';
      queryParams.value.direction = '';
    }
  }
  await getTableData();
};

Complete code for useTableData.js


export function useTableData(apiInterface, queryParams, options = {}) {
  if (!isRef(queryParams)) throw new Error('queryParams 参数必修为 Ref 类型');
  const loading = ref(false);
  const tableData = shallowRef([]);
  const totalElements = ref(0);

  const getTableData = async () => {
    try {
      loading.value = true;
      const {
        data: { content, totalElements: total },
      } = await apiInterface(queryParams.value);
      if (isFunction(options.handleContent)) {
        tableData.value = content.map(options.handleContent);
      } else {
        tableData.value = content;
      }
      if (isFunction(options.callback)) {
        options.callback(content);
      }
      totalElements.value = total;
    } finally {
      loading.value = false;
    }
  };

  const onTableChange = async (page, _, sorter) => {
    queryParams.value.pageIndex = page.current || 1;
    queryParams.value.pageSize = page.pageSize || 10;
    if (sorter && Object.keys(sorter).length > 0) {
      if (sorter.order) {
        queryParams.value.orderBy = sorter.field;
        queryParams.value.direction = sorter.order.slice(0, -3);
      } else {
        queryParams.value.orderBy = '';
        queryParams.value.direction = '';
      }
    }
    await getTableData();
  };

  const search = async () => {
    queryParams.value.pageIndex = 1;
    await getTableData();
  };

  const resetSearch = async () => {
    if (isFunction(options.resetParams)) {
      options.resetParams();
      await getTableData();
    }
  };

  const getPaginationOptions = () => {
    return {
      total: totalElements.value,
      current: queryParams.value.pageIndex,
      pageSize: queryParams.value.pageSize,
      showQuickJumper: true,
      showSizeChanger: true,
      showTotal: _total => `共 ${_total} 条数据`,
    };
  };

  return {
    loading,
    totalElements,
    tableData,
    search,
    resetSearch,
    getPaginationOptions,
    getTableData,
    onTableChange,
  };
}

Postscript | Complete Code for useTableData.ts

Based on reader feedback from Nuggets, the ts version of the code is now updated for use. The original text has explained the relevant packaging ideas ~


interface Options {
  handleContent?(content: Record<string, unknown>[]): void;
  callback?(content: Record<string, unknown>[]): void;
  resetParams?(): void;
  contentKey?: string;
  totalElementsKey?: string;
  pageSizeOptions?: string[];
}

export function useTableData(apiInterface: Function, queryParams: Ref<Record<string, unknown>>, options?: Options) {
  const loading = ref<boolean>(false);
  const tableData = shallowRef<Record<string, unknown>[]>([]);
  const totalElements = ref<number>(0);

  const getTableData = async () => {
    try {
      loading.value = true;
      const { data } = await apiInterface(queryParams.value);
      const content = data[options?.contentKey ? options.contentKey : 'content'];
      if (isFunction(options?.handleContent)) {
        tableData.value = content.map(options?.handleContent);
      } else {
        tableData.value = content;
      }
      isFunction(options?.callback) && options?.callback(content);
      totalElements.value = options?.totalElementsKey ? data[options.totalElementsKey] : data.totalElements;
    } finally {
      loading.value = false;
    }
  };

  const onTableChange: TableProps['onChange'] = async (page, _, sorter) => {
    queryParams.value.pageIndex = page.current || 1;
    queryParams.value.pageSize = page.pageSize || 10;
    if (sorter && Object.keys(sorter).length > 0) {
      // @ts-ignore
      if (sorter.order) {
        // @ts-ignore
        queryParams.value.orderBy = sorter.field as string;
        // @ts-ignore
        queryParams.value.direction = sorter.order.slice(0, -3) as string;
      } else {
        queryParams.value.orderBy = '';
        queryParams.value.direction = '';
      }
    }
    await getTableData();
  };

  const search = async () => {
    queryParams.value.pageIndex = 1;
    await getTableData();
  };

  const resetSearch = async () => {
    if (isFunction(options?.resetParams)) {
      options?.resetParams();
      await getTableData();
    }
  };

  const getPaginationOptions = () => {
    return {
      total: totalElements.value,
      current: queryParams.value.pageIndex,
      pageSize: queryParams.value.pageSize,
      showQuickJumper: true,
      showSizeChanger: true,
      pageSizeOptions: options?.pageSizeOptions ? options.pageSizeOptions : ['10', '20', '30', '40'],
      showTotal: _total => `共 ${_total} 条数据`,
    };
  };

  return {
    loading,
    totalElements,
    tableData,
    getTableData,
    onTableChange,
    search,
    resetSearch,
    getPaginationOptions,
  };
}

Related posts

View all →