We can understand the source code - list page common hook packaging

This article is the sixteenth in a series of ahooks source code articles that have been compiled into documents- address . I think it's not bad. Give me one star Support me, thank you.

List page common elements

For some background management systems, a typical list page includes three parts: filtering form items, Table tables, and Pagination.

For systems using Antd, the ahooks are mainly encapsulated by two hooks: useAntdTable and usePagination.

usePagination

usePagination is implemented based on useRequest and encapsulates common paging logic.

First, process the request through useRequest. The data structure returned by the service contract is {total: number, list: Item []}.

The first parameter of the defaultParams parameter of useRequest is {current: number, pageSize: number}. And obtains the total number of pages according to the requested parameters and the returned total value.

There is also the refreshDeps change, which will reset the current to "changeCurrent(1)" on the first page and reissue the request. Generally, you can put the conditions that pagination depends on here.

const usePagination = <TData extends Data, TParams extends Params>(
  service: Service<TData, TParams>,
  options: PaginationOptions<TData, TParams> = {},
) => {
  const { defaultPageSize = 10, ...rest } = options;

  // The data structure returned by the service is {total: number, list: Item []}
  const result = useRequest(service, {
    // The first parameter of the service is {current: number, pageSize: number}
    defaultParams: [{ current: 1, pageSize: defaultPageSize }],
    // refreshDeps changes, resets current to the first page, and relaunches the request. Generally, you can put the conditions on which pagination depends here
    refreshDepsAction: () => {
      // eslint-disable-next-line @typescript-eslint/no-use-before-define
      changeCurrent(1);
    },
    ...rest,
  });
    // Get relevant request parameters
  const { current = 1, pageSize = defaultPageSize } = result.params[0] || {};
  // Get the request result, total represents the total number of data
  const total = result.data?.total || 0;
  // Get the total number of pages
  const totalPage = useMemo(() => Math.ceil(total / pageSize), [pageSize, total]);
}

Focus on the onChange method:

  • The input parameters are the current number of pages and the maximum number of each page.
  • Calculate the total number of pages according to total.
  • Get all the parameters and execute the request logic.
  • When modifying the current page or the maximum number of each page, call the onChange method directly.
// c. Represents current page
// p. Represents page size
const onChange = (c: number, p: number) => {
  let toCurrent = c <= 0 ? 1 : c;
  const toPageSize = p <= 0 ? 1 : p;
  // Calculate the total number of pages according to total
  const tempTotalPage = Math.ceil(total / toPageSize);
  // If the total page is smaller than the current page at this time, the current page needs to be assigned as the total number of pages
  if (toCurrent > tempTotalPage) {
    toCurrent = Math.max(1, tempTotalPage);
  }

  const [oldPaginationParams = {}, ...restParams] = result.params || [];

  // Re execute request
  result.run(
    // Pay attention to parameter changes, mainly due to changes in the current number of pages and the total number of pages per page
    {
      ...oldPaginationParams,
      current: toCurrent,
      pageSize: toPageSize,
    },
    ...restParams,
  );
};

const changeCurrent = (c: number) => {
  onChange(c, pageSize);
};

const changePageSize = (p: number) => {
  onChange(current, p);
};

Finally, the result of the request and the paging field are returned, including all paging information. In addition, there are functions to operate paging.

return {
  ...result,
  // It will additionally return the pagination field, which contains all pagination information and functions to operate pagination.
  pagination: {
    current,
    pageSize,
    total,
    totalPage,
    onChange: useMemoizedFn(onChange),
    changeCurrent: useMemoizedFn(changeCurrent),
    changePageSize: useMemoizedFn(changePageSize),
  },
} as PaginationResult<TData, TParams>;

Summary: the default usage of usePagination is the same as that of useRequest, but it internally encapsulates the logic related to paging requests. The returned result returns more than one pagination parameter, including all paging information and the functions that operate paging.

The disadvantage is that the API request parameters are limited. For example, the input parameter structure must be {current: number, pageSize: number}, and the return result is {total: number, list: Item []}.

useAntdTable

useAntdTable is based on the useRequest implementation, encapsulates the common Ant Design Form and Ant Design Table linkage logic, and supports antd v3 and v4 at the same time.

First, call usePagination to handle paging logic.

const useAntdTable = <TData extends Data, TParams extends Params>(
  service: Service<TData, TParams>,
  options: AntdTableOptions<TData, TParams> = {},
) => {
  const {
    // form instance
    form,
    // Default form options
    defaultType = 'simple',
    // Default parameters: the first item is pagination data, and the second item is form data. [pagination, formData]
    defaultParams,
    manual = false,
    // refreshDeps changes, resets current to the first page, and initiates the request again.
    refreshDeps = [],
    ready = true,
    ...rest
  } = options;

  // Process the logic of paging
  // Paging is also a re encapsulation of useRequest
  const result = usePagination<TData, TParams>(service, {
    manual: true,
    ...rest,
  });
  // ...
}

Then process the logic of filtering the Form form on the list page. Antd v3 and Antd v4 versions are supported here.

// Determine whether it is the fourth version of Antd
const isAntdV4 = !!form?.getInternalHooks;

Get the current form value, form Getfieldsvalue or form.getFieldInstance:

// Get the current from value
const getActivetFieldValues = () => {
  if (!form) {
    return {};
  }
  // antd 4
  if (isAntdV4) {
    return form.getFieldsValue(null, () => true);
  }
  // antd 3
  const allFieldsValue = form.getFieldsValue();
  const activeFieldsValue = {};
  Object.keys(allFieldsValue).forEach((key: string) => {
    if (form.getFieldInstance ? form.getFieldInstance(key) : true) {
      activeFieldsValue[key] = allFieldsValue[key];
    }
  });
  return activeFieldsValue;
};

Verify form logic form.validateFields:

// Verification logic
const validateFields = (): Promise<Record<string, any>> => {
  if (!form) {
    return Promise.resolve({});
  }
  const activeFieldsValue = getActivetFieldValues();
  const fields = Object.keys(activeFieldsValue);
  // antd 4
  // validateFields direct call
  if (isAntdV4) {
    return (form.validateFields as Antd4ValidateFields)(fields);
  }
  // antd 3
  return new Promise((resolve, reject) => {
    form.validateFields(fields, (errors, values) => {
      if (errors) {
        reject(errors);
      } else {
        resolve(values);
      }
    });
  });
};

Reset form form.setFieldsValue:

// Reset Form 
const restoreForm = () => {
  if (!form) {
    return;
  }
  // antd v4
  if (isAntdV4) {
    return form.setFieldsValue(allFormDataRef.current);
  }
  // antd v3
  const activeFieldsValue = {};
  Object.keys(allFormDataRef.current).forEach((key) => {
    if (form.getFieldInstance ? form.getFieldInstance(key) : true) {
      activeFieldsValue[key] = allFormDataRef.current[key];
    }
  });
  form.setFieldsValue(activeFieldsValue);
};

Modify the form type to support 'simple' and 'advance'. The initialized form data can be filled with the full amount of form data of simple and advance. The developer can set the form data according to the currently activated type. The form form data will be reset when the type is modified.

const changeType = () => {
  // Get the current form value
  const activeFieldsValue = getActivetFieldValues();
  // Modify form values
  allFormDataRef.current = {
    ...allFormDataRef.current,
    ...activeFieldsValue,
  };
  // Set form type
  setType((t) => (t === 'simple' ? 'advance' : 'simple'));
};

// If the type is modified, the form form data will be reset
useUpdateEffect(() => {
  if (!ready) {
    return;
  }
  restoreForm();
}, [type]);

_ submit method: after verifying the form, search the form data according to the current form data, pagination and other filtering conditions.

const _submit = (initPagination?: TParams[0]) => {

  setTimeout(() => {
    // Check first
    validateFields()
      .then((values = {}) => {
        // Paging logic
        const pagination = initPagination || {
          pageSize: options.defaultPageSize || 10,
          ...(params?.[0] || {}),
          current: 1,
        };
        // If there is no form, the request is made directly according to the logic of paging
        if (!form) {
          // @ts-ignore
          run(pagination);
          return;
        }
        // Get the Data parameters of all current form s
        // record all form data
        allFormDataRef.current = {
          ...allFormDataRef.current,
          ...values,
        };

        // @ts-ignore
        run(pagination, values, {
          allFormData: allFormDataRef.current,
          type,
        });
      })
      .catch((err) => err);
  });
};

In addition, when the table triggers the onChange method, it will also make a request:

// onChange event of Table component
const onTableChange = (pagination: any, filters: any, sorter: any) => {
  const [oldPaginationParams, ...restParams] = params || [];
  run(
    // @ts-ignore
    {
      ...oldPaginationParams,
      current: pagination.current,
      pageSize: pagination.pageSize,
      filters,
      sorter,
    },
    ...restParams,
  );
};

During initialization, the request logic will be executed according to whether there is currently cached data. If there is, the request logic will be executed according to the cached data. Otherwise, determine whether to reset the form and execute the request logic through manual and ready.

// Initialization logic
// init
useEffect(() => {
  // if has cache, use cached params. ignore manual and ready.
  // params. Length > 0, it indicates that there is a cache
  if (params.length > 0) {
    // Use cached data
    allFormDataRef.current = cacheFormTableData?.allFormData || {};
    // Execute the request after resetting the form
    restoreForm();
    // @ts-ignore
    run(...params);
    return;
  }
  // If it is not manual and already ready, execute_ submit
  if (!manual && ready) {
    allFormDataRef.current = defaultParams?.[1] || {};
    restoreForm();
    _submit(defaultParams?.[0]);
  }
}, []);

Finally, the data returned from the request is transparently transmitted back to the Table component through dataSource, pagination and loading to display the data and status of the Table. And expose some operation methods of the Form form to the developer.

return {
  ...result,
  // The data required by the Table component can be directly transmitted to the Table component
  tableProps: {
    dataSource: result.data?.list || defaultDataSourceRef.current,
    loading: result.loading,
    onChange: useMemoizedFn(onTableChange),
    pagination: {
      current: result.pagination.current,
      pageSize: result.pagination.pageSize,
      total: result.pagination.total,
    },
  },
  search: {
    // Submit Form 
    submit: useMemoizedFn(submit),
    // Current form type, simple | advance
    type,
    // Switch form type
    changeType: useMemoizedFn(changeType),
    // Reset current form
    reset: useMemoizedFn(reset),
  },
} as AntdTableResult<TData, TParams>;

This article has been collected by individuals Blog Welcome to~

Tags: Javascript Front-end React TypeScript

Posted by DeathStar on Fri, 02 Sep 2022 01:40:00 +0300