背景
最近接到一个需求,大概是对一些稿子操作分散在各个页面的表单,需要来回操作,效率低,希望可以通过将这些运营操作,放入一个表单集中提交管理和维护,大致实现如下demo:
这里定义两个自定义表单组件: 运营动作:TagButton
,相关文章:ArticleFormList
从上图可以看到他们是有联动关系的:
TagButton
高亮选中时,至少存在一篇文章是对应运营动作; 2.TagButton
手动点击高亮时,相当于全选按钮,高亮所有文章对应运营动作;ArticleFormList
列表第一选中某项运营动作,对应TagButton
按钮应该高亮,表示这些稿子存在这些运营动作;ArticleFormList
列表从存在有某项运营动作,到全都没有,应该取消TagButton
按钮高亮,表示这些稿子没有这种运营动作;
当然实际需求还要比这些更复杂 ╮( ̄▽ ̄)╭
思考
说一下思考: 这个需求有两块代码实现逻辑:
- 如何实现表单自定义受控组件;
- 它们之间联动关系应该写在哪里比较合适;
对(1)来说:不要将自定义受控组件写成既受,又非控组件 :我们应该用父级传入的value
和onChange
去驱动组件值和改变,不要再定义额外的state
值,这样会额外去对内部状态state
值,与表单formStore value
值做双向绑定是一种既受,又非控的状态;
受控组件(听话的孩子):内部状态和改变状态函数由父组件提供,受父组件控制,自身不维护状态改变。
非受控组件(独立的孩子):内部状态值与父级组件隔离,父级组件无法插手干预孩子状态改变,由孩子自己管理自身状态。
表单中既受,又非控的写法是有问题的,很明显,表单项的值最后都交到formStore
手里才能提交表单,你只需它给你value
值,子组件去渲染,子组件事件回调发生变化通知formStore
,onChange
改变值。所以需要定义成受控组件。
对(2)来说:. 不要滥用useEffect
,去做响应式联动逻辑 :用useEffect
前先考虑是否可以使用事件回调函数,这一点其实在react官网也提到了,可以看这篇;
核心代码
1. TagButton 错误代码示例:
- 接收父级
value
和onChange
同时,内部又去定义自身状态checkedValueList
,然后再用useEffect
对formStore`` value
和checkedValueList
状态,做一个双向关联绑定; - 用
useWatch
和useEffect
去做表单组件之间响应联动逻辑;
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 正确代码示例:
value
和onChange
直接绑定到组件值和事件回调上面,- 响应联动逻辑,可以直接在事件回调函数处理,直接在手动触发回调函数处理即可,能不使用
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前端工程师了,这也是日后努力的目标。