antd FormItem render props使用——记一次项目迭代的思考过程

827 阅读1分钟

记录一次代码迭代的思路

功能主体是一个筛选表单,筛选项基本都是Select,筛选项间有联动,使用antd组件

表单部分封装了一个Form外壳以及查询重置等功能,没什么好说的,筛选项部分因为赶工期堆了屎,直接做成了这样

直接使用Select

怎么笨怎么写的,问题一目了然

  1. pageSize 999
  2. 每个使用的地方全都有请求接口的代码
// 代码辣眼睛,无法搜索,每个需要筛选的地方都需要cv

使用封装后的SearchInput

使用示例代码中的SearchInput稍微改造了一下作为Field,规避了pageSize===999的问题 将接口请求放进了具体的FilterItem内,能去掉大片的重复代码

但是代码仍然有些冗余,比如筛选的联动只是在外部监听onChange然后清空后置选项,能否在FormItem内就能实现这种联动呢?

const lackDepend = React.useMemo(
    () => depend.find((name) => !params[name]),
    [depend, params]
);

const searchAction = useCallback(
    (search = '') => {
     // ...
    },
    [params, lackDepend]
);

return (
    <Form.Item label='所属项目' name={name} {...rest}>
      <SearchInput
        placeholder={
          lackDepend ? `请先选择${dependLabels[lackDepend]}` : '请选择'
        }
        action={searchAction}
        disabled={!!lackDepend}
        {...searchProps}
      />
    </Form.Item>
);

当前表单内数据联动

问题的关键就是FormItem内能否获取到其他FormItem的数据。两个方案: FormItem的 children render props 参数是 FormInstance useFormInstance hook

再次封装了相同逻辑后,具体的FilterItem逻辑变得更为简单

const searchAction = (search = '', params: any) => {
  // 访问接口,处理数据
  // TODO searchAction 的代码是否可以继续封装呢
  return fetchQueryProjects(...);
};

const ProjectFilter: React.FC<ComposeFilterProps> = ({
  name = 'projectId',
  ...rest
}) => {
  return (
    <FilterItem label='所属项目' name={name} action={searchAction} {...rest} />
  );
};

render props需要是一个纯函数,无法在这一层比较params,只能传入SearchInput内比较

    <Form.Item dependencies={dependencies} noStyle>
      {(form) => {
        const params = form.getFieldsValue(dependencies);
        const lackDepend =
          dependNotNull && dependencies.find((name) => !params[name]);
        return (
          <Form.Item name={name} {...rest}>
            {/** ... */}
          </Form.Item>
        );
      }}
    </Form.Item>

改用hook的获取FormInstance,并且将表单内的联动逻辑尽可能放在组件内

  const form = Form.useFormInstance();
  // TODO deepCompare?
  const coordinateReset = useMemo(
    () => coordinate.reduce((pre, key) => ({ ...pre, [key]: undefined }), {}),
    [coordinate]
  );

  /** 是否可以同步更新label onchange不应该更新name */
  const syncLabel = labelName && name !== labelName;
  /** 是否可以同步更新value onchange不应该更新name */
  const syncValue = valueName && name !== valueName;

  return (
    <Form.Item dependencies={dependencies} noStyle>
      {() => {
        const params = form.getFieldsValue(dependencies);
        const lackDepend =
          dependNotNull && dependencies.find((name) => !params[name]);

        return (
          <Form.Item name={name} {...rest}>
            <SearchInput
              disabled={!!lackDepend}
              action={action}
              defaultOptions={defaultOptions}
              placeholder={
                lackDepend ? `请先选择${dependLabels[lackDepend]}` : '请选择'
              }
              params={params}
              {...searchProps}
              onChange={(newValue, option: any) => {
                form.setFieldsValue({
                  ...coordinateReset,
                  ...(syncLabel && { [labelName]: option.label }),
                  ...(syncValue && { [valueName]: option.value }),
                });
                
                searchProps?.onChange?.(newValue, option);
              }}
            />
          </Form.Item>
        );
      }}
    </Form.Item>
  );

TODO

ComposeFilter或FilterItem这一层能否避免无效的rerender?是否可以用key来解决? 使用高阶组件来封装而不是普通组件?