Watch,一种更优雅的监听 antd Form 字段变化的方式

2022-12-07 17:58:05 +08:00
 nanxiaobei

使用 antd Form 时,监听某个字段变化,并根据不同字段值渲染不同的 UI ,是个非常常见的需求。

那么在 antd Form 中,如何监听某个字段的变化呢?

1. 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 也就不更新了。

2. 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.setFieldsValueform.resetFields 等更新表单数据时,songValue 并不会更新。

此时就需要在执行 form.setFieldsValue 等的地方,相应的触发 setSongValue

form.setFieldsValue 在很高的父组件中执行时,又需要将 songValue 状态提示,为避免这种麻烦,我们更倾向于在子组件 useEffect 中处理变化。

使用 useEffect 不仅触发了多余的一次 re-render ,而且,假如有很多字段,需要在多处处理呢?(实际开发中的常见情况)

我们需要添加大量的重复性代码,写来写去,又忘了哪里没加、哪里需要加、哪里不需要加,最终,更新逻辑会变得一团混乱。

3. Form.useWatch

一开始强调在 antd@4.20.0 之前,是因为从 antd@4.20.0 开始,antd Form 添加了一个新的 API Form.useWatch,用于处理此种情况。

此时,songValue 就可以响应 form.setFieldsValueform.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 。

那么有没有更优雅的 "局部更新" 的方案呢?

4. Watch 组件,来自 Ant Plus 5

Ant 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

5. Watch API 介绍

Watch 还可以使用 list 以监听多个字段。

namelist 互斥,因为 antdname(NamePath) 也支持数组形式,故用 list 来区分数组的不同含义。

children & onlyValidonChange 互斥。

使用 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 -

欢迎尝试 antxWatch 组件。

更多关于 Ant Plus 5 的信息,请查看 → https://github.com/nanxiaobei/ant-plus

2305 次点击
所在节点    React
5 条回复
LOWINC
2022-12-08 09:46:21 +08:00
貌似 shouldUpdate 可以实现类似功能

https://4x.ant.design/components/form-cn/#shouldUpdate
ragnaroks
2022-12-13 11:49:50 +08:00
fight with antdesign 实在是太浪费时间了
Jaosn
2023-01-08 23:43:27 +08:00
@ragnaroks #2 国内大部分中后台都是 Antd

请问还有健壮的库推荐吗?
ragnaroks
2023-01-09 00:00:22 +08:00
@Jaosn
按本人喜好排名,tailwindUI 、fluent-ui 、mantine 、blueprint 、chakra-ui 、MUI ,这些都是久经考验的库。

但是一般而言,专业前端不会使用任何 UI 组件库,组件库是跟不上业务需求的,而且没有 KPI 。
mufeng
2023-08-24 16:01:26 +08:00
onValuesChange 不就可以吗

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/900854

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX