使用 antd
Form 时,监听某个字段变化,并根据不同字段值渲染不同的 UI ,是个非常常见的需求。
那么在 antd
Form 中,如何监听某个字段的变化呢?
form.getFieldValue
在 antd@4.20.0
之前,假设要监听 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
一开始强调在 antd@4.20.0
之前,是因为从 antd@4.20.0
开始,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。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.