Watch,一种更优雅的监听 antd Form 字段变化的方式 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
nanxiaobei
V2EX    React

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

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

    使用 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  
       2023-08-24 16:01:26 +08:00
    onValuesChange 不就可以吗
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     907 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 21:08 PVG 05:08 LAX 13:08 JFK 16:08
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86