使用 antd Form 时,监听某个字段变化,并根据不同字段值渲染不同的 UI ,是个非常常见的需求。
那么在 antd Form 中,如何监听某个字段的变化呢?
form.getFieldValue在 [email protected] 之前,假设要监听 song 字段的变化,我们很容易写出这样的代码:
const [form] = Form.useForm();
const songValue = form.getFieldValue('song');
<Form form={form}>
<Form.Item label="歌曲" name="song">
<Input />
</Form.Item>
{songValue?.length > 0 && <div>歌曲:{songValue}</div>}
</Form>;
使用 form.getFieldValue 有什么问题呢?
问题就是 form.getFieldValue 取到的值,并不会并触发 UI 更新,简单来说,不是一个 state (随着 antd 升级其 Form 行为有过变化,此处不讨论旧版本行为)。
那为什么有时候,又确实看到 UI 更新了呢?
这是因为实际业务代码中,可能会请求多个接口(多次 setState),也可能 Form 会被父级更新触发 re-render (总之,我们都知道一个 React 组件的 re-render 次数是非常不可控的,尤其是代码写的很烂时 😜)。
所以并不是 songValue 触发了 UI 更新,而是在新的 re-render 中,songValue 连带着被更新了。
这里就是非常容易产生 bug 的一个点,可能开发时 UI 是正常的,但正如上面所说,"re-render 次数非常不可控",可能某次 re-render 未被触发,songValue 相关 UI 也就不更新了。
useState我们发现了一个 bug ,UI 竟然不更新!于是自然而然的想到:把 song 变成一个 state 。
const [form] = Form.useForm();
const [songValue, setSongValue] = useState('');
<Form form={form}>
<Form.Item label="歌曲" name="song">
<Input onChange={(event) => setSongValue(event.target.value)} />
</Form.Item>
{songValue?.length > 0 && <div>歌曲:{songValue}</div>}
</Form>;
使用 useState 有什么问题呢?
简单来说,此处 songValue 并不是响应式的,当用 Form 内置方法更新 song 时,songValue 相关 UI 并不会更新。
例如当 song 与 props 变化有关,或与接口数据变化有关,使用 form.setFieldsValue、form.resetFields 等更新表单数据时,songValue 并不会更新。
此时就需要在执行 form.setFieldsValue 等的地方,相应的触发 setSongValue。
当 form.setFieldsValue 在很高的父组件中执行时,又需要将 songValue 状态提示,为避免这种麻烦,我们更倾向于在子组件 useEffect 中处理变化。
使用 useEffect 不仅触发了多余的一次 re-render ,而且,假如有很多字段,需要在多处处理呢?(实际开发中的常见情况)
我们需要添加大量的重复性代码,写来写去,又忘了哪里没加、哪里需要加、哪里不需要加,最终,更新逻辑会变得一团混乱。
Form.useWatch一开始强调在 [email protected] 之前,是因为从 [email protected] 开始,antd Form 添加了一个新的 API Form.useWatch,用于处理此种情况。
此时,songValue 就可以响应 form.setFieldsValue、form.resetFields 等的更新了。
const [form] = Form.useForm();
const songValue = Form.useWatch('song', form);
<Form form={form}>
<Form.Item label="歌曲" name="song">
<Input />
</Form.Item>
{songValue?.length > 0 && <div>歌曲:{songValue}</div>}
</Form>;
使用 Form.useWatch 有什么问题呢?(怎么还有问题!)
其实不是问题,主要是性能不好。因为 Form.useWatch 其实就是把 songValue 变为了一个 state ,然后内部处理了表单联动。
但 state 的问题就是,它会触发整个组件的 re-render ,进行不必要的 diff ,如果组件很大而且是监听 Input 实时输入,这种性能消耗是很恐怖的,每次按键都是一次全量 diff 。
而这种 re-render 其实毫无意义,因为我们 "精准" 的知道,就是要监听 song 字段的变化,根据 song 的值来更新 "局部" 的 UI ,而不是更新整体 UI 。
那么有没有更优雅的 "局部更新" 的方案呢?
Watch 组件,来自 Ant Plus 5Ant Plus 5 (antx)中提供了一个 Watch 组件,专用于监听表单字段变化,并更新局部 UI 的需求。
使用 antx 组件,可以简化 antd Form 代码,那么监听 song 的代码将如下:
import { Form, Watch, Input } from 'antx';
const [form] = Form.useForm();
<Form form={form}>
<Input label="歌曲" name="song" />
<Watch name="song">
{(songValue) => {
// 仅此处 UI 更新,不会每次输入都触发整个组件 re-render
return songValue?.length > 0 && <div>歌曲:{songValue}</div>;
}}
</Watch>
</Form>;
使用 Watch,就可以避免 Form.useWatch 不停全量 re-render 的性能问题,同时,也不需要在 useEffect 中处理更新逻辑。
使用 Watch,就只有 render props 中返回的 UI 会更新,不会联动整个组件不停的 re-render 。
在线示例 → https://codesandbox.io/s/antx-v4hqw
Watch API 介绍Watch 还可以使用 list 以监听多个字段。
name 与 list 互斥,因为 antd 的 name(NamePath) 也支持数组形式,故用 list 来区分数组的不同含义。
children & onlyValid 与 onChange 互斥。
使用 onlyValid 可在 children 函数中只拿到非 undefined 的 "有效值"。而 onChange 中可执行 setState。
| Props | 说明 | 类型 | 默认值 |
|---|---|---|---|
name |
需监听的字段 | NamePath |
- |
list |
需监听的字段列表 (与 name 互斥) |
NamePath[] |
- |
children |
Render props 形式。获取被监听的值(或列表),返回 UI | (value: any) => ReactNode |
- |
onlyValid |
被监听的值非 undefined 时,才触发 children 渲染 |
boolean |
false |
onChange |
获取被监听的值(或列表),处理副作用 (与 children 互斥) |
(value: any) => void |
- |
欢迎尝试 antx 的 Watch 组件。
更多关于 Ant Plus 5 的信息,请查看 → https://github.com/nanxiaobei/ant-plus。
1
LOWINC 2022-12-08 09:46:21 +08:00
貌似 shouldUpdate 可以实现类似功能
https://4x.ant.design/components/form-cn/#shouldUpdate |
2
ragnaroks 2022-12-13 11:49:50 +08:00
fight with antdesign 实在是太浪费时间了
|
4
ragnaroks 2023-01-09 00:00:22 +08:00
@Jaosn
按本人喜好排名,tailwindUI 、fluent-ui 、mantine 、blueprint 、chakra-ui 、MUI ,这些都是久经考验的库。 但是一般而言,专业前端不会使用任何 UI 组件库,组件库是跟不上业务需求的,而且没有 KPI 。 |
5
mufeng 2023-08-24 16:01:26 +08:00
onValuesChange 不就可以吗
|