实现一个联动表单自定义受控组件的思考

背景

最近接到一个需求,大概是对一些稿子操作分散在各个页面的表单,需要来回操作,效率低,希望可以通过将这些运营操作,放入一个表单集中提交管理和维护,大致实现如下demo:

完整示例代码demo入口

这里定义两个自定义表单组件: 运营动作:TagButton,相关文章:ArticleFormList 从上图可以看到他们是有联动关系的:

  1. TagButton 高亮选中时,至少存在一篇文章是对应运营动作; 2.TagButton手动点击高亮时,相当于全选按钮,高亮所有文章对应运营动作;
  2. ArticleFormList 列表第一选中某项运营动作,对应TagButton按钮应该高亮,表示这些稿子存在这些运营动作;
  3. ArticleFormList 列表从存在有某项运营动作,到全都没有,应该取消TagButton按钮高亮,表示这些稿子没有这种运营动作;

当然实际需求还要比这些更复杂 ╮( ̄▽ ̄)╭

思考

说一下思考: 这个需求有两块代码实现逻辑:

  1. 如何实现表单自定义受控组件;
  2. 它们之间联动关系应该写在哪里比较合适;

对(1)来说:不要将自定义受控组件写成既受,又非控组件 :我们应该用父级传入的valueonChange 去驱动组件值和改变,不要再定义额外的state值,这样会额外去对内部状态state值,与表单formStore value值做双向绑定是一种既受,又非控的状态;

受控组件(听话的孩子):内部状态和改变状态函数由父组件提供,受父组件控制,自身不维护状态改变。
非受控组件(独立的孩子):内部状态值与父级组件隔离,父级组件无法插手干预孩子状态改变,由孩子自己管理自身状态。

表单中既受,又非控的写法是有问题的,很明显,表单项的值最后都交到formStore手里才能提交表单,你只需它给你value值,子组件去渲染,子组件事件回调发生变化通知formStoreonChange改变值。所以需要定义成受控组件。

对(2)来说:. 不要滥用useEffect,去做响应式联动逻辑 :用useEffect前先考虑是否可以使用事件回调函数,这一点其实在react官网也提到了,可以看这篇

核心代码

1. TagButton 错误代码示例

  1. 接收父级 valueonChange同时,内部又去定义自身状态checkedValueList,然后再用useEffectformStore`` valuecheckedValueList状态,做一个双向关联绑定;
  2. useWatchuseEffect 去做表单组件之间响应联动逻辑;
tsx 复制代码
 // TagButton 错误代码示例:
 const TagButton  = ({ value = [], onChange })=> {
    const [checkedValueList, setCheckedValueList] = useState([]);
    const form = Form.useFormInstance();
    const ArticleFormList = Form.useWatch('articleList', form);

    // 内容状态变化,同步到表单
    useEffect(() => {
        onChange && onChange(checkedValueList);
        // eslint-disable-next-line
    }, [checkedValueList]);

    // 表单值变化,同步到内部状态
    useEffect(() => {
        // 表单值和内部状态不同才修改内部状态
        if (Array.isArray(value) && !isEqual(value, checkedValueList)) {
            setCheckedValueList(value);
        }

        // eslint-disable-next-line
    }, [value]);

    // 响应联动,勾选和取消勾选
    useEffect(() => {
       // 实现响应联动逻辑
         ...
    }, [ArticleFormList]);

   ....
  }

2. TagButton 正确代码示例

  1. valueonChange 直接绑定到组件值和事件回调上面,
  2. 响应联动逻辑,可以直接在事件回调函数处理,直接在手动触发回调函数处理即可,能不使用useEffect就不用用它,万不得已才请它出来 (`Д´*)。
tsx 复制代码
const TagButton: FC<TagButtonProps> = ({
  value: currentSelectedList = [],
  onChange,
}) => {
  const form = Form.useFormInstance();

  const handleOnChangeCheck = (targetValue: ActionType) => {
     // 实现响应联动逻辑
         ...
        onChange && onChange(result);
  };

  return (
    <div>
      {actionTypeList.map((item, index) => {
        const currentValue = item.value;
        const isCheck = currentSelectedList.includes(currentValue);
        return (
          <Tag.CheckableTag
            key={index}
            onChange={() => handleOnChangeCheck(currentValue)}
            checked={isCheck}
          >
            {item.label}
          </Tag.CheckableTag>
        );
      })}
    </div>
  );
};

总结

这个需求可能大家都可以实现,那么如何体现你的价值呢,我想在完成需求同时,能够从代码可维护性和阅读性角度来完成实现,那么你就更有优势,相信大家都不想以后维护像(1)这种很容易出错,难以维护和阅读代码。写需求除了完成prd实现外,还需要思考代码质量,在此基础上,如果你更加优秀,能够设计出比产品交互体验更好界面,那你就是一名出色web前端工程师了,这也是日后努力的目标。

相关推荐
海上彼尚1 分钟前
实现3D热力图
前端·javascript·3d
杨过姑父1 分钟前
org.springframework.context.support.ApplicationListenerDetector 详细介绍
java·前端·spring
理想不理想v11 分钟前
使用JS实现文件流转换excel?
java·前端·javascript·css·vue.js·spring·面试
惜.己31 分钟前
Jmeter中的配置原件(四)
java·前端·功能测试·jmeter·1024程序员节
EasyNTS32 分钟前
无插件H5播放器EasyPlayer.js网页web无插件播放器vue和react详细介绍
前端·javascript·vue.js
poloma37 分钟前
五千字长文搞清楚 Blob File ArrayBuffer TypedArray 到底是什么
前端·javascript·ecmascript 6
guokanglun1 小时前
Vue.js动态组件使用
前端·javascript·vue.js
Go4doom1 小时前
vue-cli3+qiankun迁移至rsbuild
前端
-seventy-1 小时前
Ajax 与 Vue 框架应用点——随笔谈
前端
我认不到你1 小时前
antd proFromSelect 懒加载+模糊查询
前端·javascript·react.js·typescript