useEffect如何通过form.getFieldValue(‘xxx‘)监听Form表单变化
作者:小刘加油!
场景
子组件中,某一个表格的数据需要依赖于上级组件的某一个表单元素值进行计算。
毫无疑问,首先想到的肯定是监听 form 表单中元素的值,使用 useEffect
监听表单的变化,当值发生变化时,重新计算渲染。
首先说下我的代码结构:
Form 表单是一个子组件,表格组件也是一个子组件,且是比较深的子组件(包含在tab标签页下)。如果说 Form子组件是一级子组件,那么表格组件就是一个四级子组件。
在这种多层嵌套下,如何把 form 数据传递给对应的表格子组件?
毫无疑问,选择使用:useContext
在顶层组件中通过 Form.useForm()
定义 Form 实例,管理数据状态,通过 <context.Provider value={}></context.Provider>传递,表单子组件和表格子组件都可进行接收。
不过在子组件监听代码写好后,却发现了一个比较尴尬的问题:
表单元素的值发生了改变,但子组件的useEffect 却没能监听到。
// 子组件 import React, { useContext } from 'react' import context from 'xxx/xxx' const { form } = useContext<any>(context) useEffect(() => { console.log('监听到了变化') // 下面是具体的逻辑 }, [form.getFiledValue('xx')])
注意:
当时令我困惑的是:
当 Form 表单组件的某个表单元素值改变时,虽然子组件 useEffect 没有监听到变化,但通过表格子组件的输入框的 onBlur 事件,调用 form.getFiledValue('xx')
,却可以拿到最新的值。
分析
需求终归还是要往下开发的,监听不了,那就想办法监听!
在项目中,我是使用了 const [form] = Form.useForm();
来创建 Form 实例,管理表单数据状态,这也是函数式组件推荐的一种方式。
通过使用 useForm ,会让表单成为 非受控组件,不用通过 useState 创建state 来维护数据。
而且 ant 官网 里面也说了:使用这种方式与其他获取数据的方式的区别:
Form 仅会对变更的 Field 进行刷新,从而避免完整的组件刷新可能引发的性能问题,因而无法在 render 阶段通过 form.getFieldsValue
来实时获取字段值。
看到这里,其实已经很清楚了,因为 使用了 useForm
,导致表单变成了 非受控组件,不能通过 useState 来创建 state 维护数据,只能通过
form.getFieldsValue() / form.setFieldsValue()
来获取及设置表单数据,ant design 官方文档也有介绍。所以在这种情况下, 使用 useEffect 监听不到 form 表单的变化。
换句话说:
form.getFieldValue('xxx');
并不是响应式的,由其取到的值,并不会触发 UI 更新,即:它不是一个 state。
不过,可能有人会问:我之前也使用过 useEffect 来监听 form.getFieldValue(‘xxx’),UI也能正常渲染啊,你这里是不是说错了?
答: 没有说错,我自己之前也有这样的疑问,也遇到过这类问题。
在实际项目中,一个 react 组件的 re-render 次数是不可控的, 特别是代码写的不那么规范的时候,
所以,当看到 UI 更新的时候,或许是在新的 re-render 中被连带着更新了;
又或许是项目代码中 Form组件既使用了 form = {form}
属性,又使用了 initialValues
属性。
如下所示:
// const [form] = Form.useForm(); <Form initialValues={formData} form={form} ref={formRef}> </Form>
解决方案
方案1
ant design @4.20 版本推出了一个新特性:Form.useWatch,可以直接获取 form 中字段对应的值,应该是可以监听到的,具体用法可以参考官网,这里不多做赘述。
可是 4.2 版本后才支持,我的项目版本是 4.12.3,暂不支持这个hooks,部门领导也不让进行 ant 升级,所以就没有尝试这种写法。
而且使用这种方法,固然可以监听到,不过性能不是太好,不建议使用。
因为它会触发整个组件的 re-render,当组件比较大且监听 input 实时输入时,性能消耗就很恐怖。
方案2
拒绝使用 Form.useForm(),使用 initialValues 属性,是表单变成一个可控组件,拥有 state,就可以正常监听。
不过对于我的项目代码来说,改动量太大,不划算。
方案3
使用 useState
额外定义个 state
,当表单元素值发生变化时,使用 setState(),然后将其值通过 context.Provider传递,在子组件监听。
方案4
使用 useReducer
useReducer 与 useState 都是用来存储和更新 state,相当于 useState 的升级版。
当 type 数量较多时,建议使用 useReducer
方案5
Ant Plus 5 的 Watch 组件
专门用于监听表单字段变化,并更新局部UI。(没尝试过)
总结
其实核心要点就一句话:
Form 内置方法,不是响应式的(即不是一个state),由其设置或者获取的值,不会触发UI更新,只能对变更的field进行刷新。
想要对其进行监听也很简单,将其变成一个state即可。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。