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

背景

最近接到一个需求,大概是对一些稿子操作分散在各个页面的表单,需要来回操作,效率低,希望可以通过将这些运营操作,放入一个表单集中提交管理和维护,大致实现如下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前端工程师了,这也是日后努力的目标。

相关推荐
大怪v2 小时前
AI抢饭?前端佬:我要验牌!
前端·人工智能·程序员
新酱爱学习2 小时前
字节外包一年,我的技术成长之路
前端·程序员·年终总结
小兵张健2 小时前
开源 playwright-pool 会话池来了
前端·javascript·github
IT_陈寒5 小时前
Python开发者必知的5大性能陷阱:90%的人都踩过的坑!
前端·人工智能·后端
codingWhat6 小时前
介绍一个手势识别库——AlloyFinger
前端·javascript·vue.js
代码老中医6 小时前
2026年CSS彻底疯了:这6个新特性让我删掉了三分之一JS代码
前端
不会敲代码16 小时前
Zustand:轻量级状态管理,从入门到实践
前端·typescript
踩着两条虫6 小时前
VTJ.PRO 双向代码转换原理揭秘
前端·vue.js·人工智能
扉川川6 小时前
OpenClaw 架构解析:一个生产级 AI Agent 是如何设计的
前端·人工智能
远山枫谷6 小时前
一文理清页面/组件通信与 Store 全局状态管理
前端·微信小程序