V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
nanxiaobei
V2EX  ›  React

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

  •  
  •   nanxiaobei ·
    nanxiaobei · 2022-12-07 17:58:05 +08:00 · 1954 次点击
    这是一个创建于 495 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    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

    一开始强调在 [email protected] 之前,是因为从 [email protected] 开始,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

    5 条回复    2023-08-24 16:01:26 +08:00
    LOWINC
        1
    LOWINC  
       2022-12-08 09:46:21 +08:00
    貌似 shouldUpdate 可以实现类似功能

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

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

    但是一般而言,专业前端不会使用任何 UI 组件库,组件库是跟不上业务需求的,而且没有 KPI 。
    mufeng
        5
    mufeng  
       235 天前
    onValuesChange 不就可以吗
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5585 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 08:00 · PVG 16:00 · LAX 01:00 · JFK 04:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.