preface

Antd is an excellent set of React component library, which has become an infrastructure for backend projects developed by teams using React stack. It just so happened that I have been using TS+React+ ANTD to develop backend projects recently, so I want to sort out some experience and share with you.

This is a data compiled by the author before, and this ratio should not be much different in other companies, after all, most of the middle and background pages are CRUD type, new submission – form query – list display – data update this basically covers most of the common development scenarios.

Form the scene

Distinguish between new and update

New and updated form fields are for the most part highly consistent, so components can be reused, as shown in the training configuration below for an example:

Although form components can be reused, they are logically very different:

  • Updating a scene requires passing a unique ID of the list, while creating a scene does not
  • Update needs to backfill the previous data, new does not need to

This requires distinguishing between logic in the case of reusing components:

  • One, you can pass onetypeAttributes to distinguish,type Type = 'create' | 'update';
  • The second way is to directly judge what the parent component passes inpropsIs there one inUpdate the IDCarry on distinction, existence, be update, nonexistence, be new;

The second option is recommended because it is concise enough not to pass additional attributes.

A good component encapsulation principle: The simpler the props, the more maintainable it is, without affecting the implementation of component functions.

The Form backfill

React binding is much more difficult to implement than VUE’s V-Model instruction. At the same time, other design choices are also considered. Antd-form currently adopts a completely uncontrolled design, and if you want to modify the internal state of the Form, you must call the Form instance method to complete the modification. The initialValues of the Form can be used to set default values, but it has an important feature: it only takes effect once when the component is first initialized. However, in real scenarios, our backfill data is often asynchronously retrieved from the back-end interface, so some other means are needed to accomplish this work. Here are two ideas.

  • The first:initialValuesAttribute +form.resetFields()

Save the asynchronously requested backfill data as a state and call form.resetFields()

const [form] = Form.useForm(); const [detail, setDetail] = useState<Record<string, any>>(); useEffect(() => { axios.get('/api/xxx').then(data => { setDetail(data); form.resetFields(); }); }, [form]); return ( <> <Form form={form} initialValues={detail}> <Form.Item name="age" label="age"> <Input /> </Form.Item> </Form> < / a >);Copy the code
  • The second is direct useform.setFieldsValue()Update the form value.
const [form] = Form.useForm();
    useEffect(() => {
        axios.get('/api/xxx').then(data => {
            form.setFieldsValue(data);
        });
    }, [form]);
    return (
        <>
            <Form form={form}>
                <Form.Item name="age" label="age">
                    <Input />
                </Form.Item>
            </Form>
        </>
    );
Copy the code

In general, the second method is recommended and doesn’t require extra state to process, but if you want to do something else with the data you’ve retrieved, you’ll prefer the first method.

Data format conversion

The backend backfill data format is often inconsistent with what the front-end needs, especially in the form scenario.

For example, if the date field is backfilled with the timestamp value 1628897838278, the antD date component needs the Moment type by default, which is abstractly described in TypeScript interface.

The next step is to implement two conversion functions

  • Format the back-end data (ServerParamsConvert to the format required by the Form (FormValues)
  • Put the front Form (FormValues) Convert the collected data format to the format required by the back end (ServerParams)

Furthermore, I recommend that whenever you develop a form scenario, you define two transformation methods in advance to make the data transformation logic clearer at the front and back ends, and to make the form fields more complex as subsequent iterations of requirements become more profitable.

Multi-scenario submission

Most forms have only one submit main button, which can be completed with an htmlType = ‘submit’ button and the Form’s onFinish callback.

However, there are always exceptions to the rule. For example, the page is now designed to have two main actions: save draft and Submit.

  • Save draft: Data is stored temporarily and does not take effect on business in real time
  • Submit: takes effect in real time for services

This means that they are different logics, perhaps corresponding to different interfaces on the back end, so the normal process of submitting onFinish will not work because you cannot distinguish between two different logics in the same method. You can do this:

  1. Different ways to distinguish between operation types

  1. If verification is required, tuneawait form.validateFieldsVerify, get the form value by the way, if you don’t need it, call directlyform.getFieldsValueGet the form value, and then you can implement different functional logic by tuning different back-end interfaces.

The list of

Paging request

Select a public hook to handle paging:

import { useState, useEffect, useRef } from 'react'; import { message, TableProps } from 'antd'; export interface PageParams { pageNum:number pageSize:number } type RequestHandler<RecordType> = (pageParams:PageParams)  => Promise<{ data: Array<RecordType>; total: number }>; Interface Result<RecordType> {tableProps: tableProps <RecordType> // Table properties resetTable:()=>void // Manually reset the table ReloadTable :()=>void reloadTable:()=>void reloadTable:() * @param request method * @param deps table reset dependencies, any dependencies change, reset page number to 1, Return Result<T> */ export default function useAntdTable<T = object>(request:RequestHandler<T>, deps:any[] = [], tableProps:TableProps<T> = {}):Result<T> { const [dataList, setDataList] = useState([]); // List data const defaultPageSize = (tableProps? .pagination && tableProps? .pagination.defaultPageSize) || 10; const [pageParams, setPageParams] = useState({ pageNum: 1, pageSize: defaultPageSize }); Const [total, setTotal] = useState(0); Const [isFetching, setIsFetching] = useState(false); Const isInitFlagRef = useRef(true); Const fetchData = async () => {try {setIsFetching(true); const { data, total } = await request(pageParams); setDataList(data || []); setTotal(total || 0); {} the catch (error) message. The error (error. The message | | error. The MSG | | 'loading list error); setDataList([]); } isInitFlagRef.current = false; setIsFetching(false); }; useEffect(() => { fetchData(); }, [pageParams]); useEffect(() => { const isNeedRest = ! isInitFlagRef.current; IsNeedRest && setPageParams({... pageParams, pageNum: 1, }); }, deps); const onChange = ({ current, pageSize }) => { setPageParams({ pageNum: pageSize === pageParams.pageSize ? current : 1, pageSize, }); }; return { tableProps: { ... tableProps, loading: isFetching, onChange, dataSource: dataList, pagination: tableProps.pagination ! == false && { ... tableProps.pagination, current: pageParams.pageNum, pageSize: pageParams.pageSize, total, }, }, resetTable() { setPageParams({ ... pageParams, pageNum: 1, }); setTotal(0); setDataList([]); }, reloadTable() { fetchData(); setTotal(0); setDataList([]); }}; }Copy the code

With useAntdTablehook, it is very easy to use pagination in development.

The list of operations

The operation of the list and the interaction form are mostly based on the Modal box, which is triggered by button clicking, as the carrier of the operation. The list ID and other list parameters need to be passed in to realize the corresponding business logic.

Here ARE two solutions:

  • The first:The way data flows. Mainly is toModal+ operationButtonCombined into a package containing functional logicEnhanced button assemblyThe list ID is passed in as a data stream.

Below is the implementation of the action button ShowNameListBtn.

Advantages: Button components are more autonomous, with list ids and other list fields coming in as data streams without special processing. Disadvantages: Low performance, component renders as many list items as possible.

  • A specialized set of states maintains information about the piece of data that is currently being manipulated.
  1. Create a new set of states. containsModalDisplay, list ID, and other list item data.

  1. Bind an update event to the action button to update the list item information to the status mentioned in the previous step.

  1. The parent component state updates, causing a rerender, passing the latest data to the popbox component.

Advantage: Low rendering overhead. Modal only needs to be rendered once in the parent component reference. Disadvantages: the component partition is not decoupled enough, and the parent component has an additional set of state maintenance. The list ID parameters should not strictly fall within the state of the parent component.

The author generally chooses the first kind more, mainly sees this kind of program: makes the component responsibility more clear, the button component realizes autonomy, can greatly reduce the code amount of the parent component.

Continuously updated…

The demand does not stop, the article will continue to update… I hope you can exchange your experience in the comments section, or give criticism.