React Hook 是函数式组件开发模式,没有组件的实例,没有 this 指向,因此无法使用组件的实例,去调用组件中的方法,这与 Vue 中的 Hook 组件有很大的不用。不过官方也提供了对应的 Hook 函数解决这类问题,本文就讲讲如何使用 forwardRef 和 useImperativeHandle 来实现父组件调用子组件中的方法。
一、使用场景 使用 React Hook + antd 开发页面经常遇到这样的情况,一个列表的数据有新增,编辑(更新)和删除等操作。新增和编辑通常都是弹窗中提交 form 表单数据。新增时 form 表单中默认为空,编辑时回显旧数据。 所以这就涉及到了父子组件通信问题,需要在父组件中控制子组件弹窗的展示隐藏,编辑的时候还需要传入回显的数据。 单纯的使用 props 传参可以实现,但是需要大量的使用 useEffect 来监听数据的变化,进行对应的响应式操作。这种形式显然不够直接,不够友好。 需求:在父组件直接调用子组件中的方法,来达到响应式的效果。
二、forwardRef 和 useImperativeHandle API forwardRef forwardRef 是一个React API,用于在函数式组件中向子组件传递ref。通常,在函数式组件中,你不能直接访问子组件的DOM元素或实例。通过forwardRef,你可以将一个ref对象传递给子组件,从而可以在父组件中引用或操作子组件。
具体工作方式如下:
通过forwardRef,你可以创建一个包装函数组件,它接受props和ref参数,并返回子组件的JSX。 这个包装组件可以在渲染时将ref传递给子组件的DOM元素或子组件实例。 父组件可以通过这个ref来访问或操作子组件。 示例:
1 2 3 4 const MyComponent = forwardRef ((props, ref ) => { return <ChildComponent ref ={ref} /> ; });
useImperativeHandle useImperativeHandle 是一个React Hook,通常与 forwardRef 一起使用。它用于自定义将从子组件暴露给父组件的方法或属性。具体作用如下:
useImperativeHandle 接受两个参数,第一个参数是ref对象,第二个参数是一个回调函数。 在回调函数中,你可以定义要暴露给父组件的方法或属性,并返回一个包含这些方法或属性的对象。 这些方法或属性可以在父组件中通过ref对象访问。 示例:
1 2 3 4 5 6 useImperativeHandle (ref, () => ({ customMethod : () => { }, customProperty : someValue, }));
forwardRef 和 useImperativeHandle 结合使用可以让你更好地控制父子组件之间的通信和行为。forwardRef 用于传递 ref,而 useImperativeHandle 用于定义要暴露给父组件的方法或属性。
三、父组件调用子组件的方法 创建子组件 首先,创建子组件 ChildComponent,其中使用 forwardRef 和 useImperativeHandle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import React , { forwardRef, useImperativeHandle, useRef, useState } from 'react' ;import { Modal , Form , Input , Button } from 'antd' ;const ChildComponent = (props, ref ) => { const [form] = Form .useForm (); const [visible, setVisible] = useState (false ); useImperativeHandle (ref, () => ({ form, show, hide : () => { form.resetFields (); setVisible (false ); }, })); const show = (data ) => { form.resetFields (); if (data) { form.setFieldsValue (data); } setVisible (true ); } const handleOk = ( ) => { form .validateFields () .then ((values ) => { console .log ('表单数据' , values); form.resetFields (); setVisible (false ); }) .catch ((errorInfo ) => { console .log ('表单校验失败:' , errorInfo); }); }; const handleCancel = ( ) => { form.resetFields (); setVisible (false ); }; return ( <Modal open ={visible} title ="编辑信息" onOk ={handleOk} onCancel ={handleCancel} footer ={[ <Button key ="back" onClick ={handleCancel} > 取消 </Button > , <Button key ="submit" type ="primary" onClick ={handleOk} > 保存 </Button > , ]} > <Form form ={form} layout ="vertical" > <Form.Item name ="name" label ="名称" rules ={[{ required: true , message: '请输入名称 ' }]} > <Input /> </Form.Item > {/* 其他表单字段 */} </Form > </Modal > ); };export default forwardRef (ChildComponent );
在父组件中使用子组件 在父组件 ParentComponent 中,你可以通过 ref 控制子组件的显示和隐藏,并在编辑时传递旧数据给子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import React , { useRef, useState } from 'react' ;import { Button } from 'antd' ;const ParentComponent = ( ) => { const childRef = useRef (null ); const handleShowModal = (data ) => { childRef.current .show (data); }; return ( <div > <Button onClick ={() => handleShowModal({ name: '旧数据' })}>编辑数据</Button > <ChildComponent ref ={childRef} /> </div > ); };export default ParentComponent ;
在这个示例中,handleShowModal 函数接受一个 data 参数,该参数包含要编辑的旧数据。然后,它调用 childRef.current.show(data) 来传递旧数据给子组件。在子组件的 show 方法中,我们使用 form.setFieldsValue(data) 来设置表单字段的值为旧数据,以便在编辑时显示这些数据。
欢迎访问:天问博客