基于useImperativeHandle的使用解析
目录
- useImperativeHandle的使用
- useImperativeHandle
- 使用useImperativeHandle时父组件第一次没拿到子组件方法
- 背景需求
- 问题
- 解决方法
useImperativeHandle的使用
你不能在函数组件上使用 ref 属性,因为它们没有实例:
import React, { Component } from 'react'; function MyFunctionComponent() { return <input /> } class Parent extends React.Component { constructor(props) { super(props); this.textInput = React.createRef(); } render() { return ( <MyFunctionComponent ref={this.textInput} /> ); } }
如果你需要使用 ref,你应该将组件转化为一个 class,就像当你需要使用生命周期钩子或 state 时一样。
不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:
function CustomTextInput(props) { // 这里必须声明 textInput,这样 ref 才可以引用它 let textInput = React.createRef(); function handleClick() { textInput.current.focus(); } return ( <div> <input type="text" ref={textInput} /> <input type="button" value="Focus the text input" onClick={handleClick} /> </div> ); }
在下面的示例中,MyFunctionComponent 使用 React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM button:
const MyFunctionComponent = React.forwardRef((props, ref) => ( <button ref={ref}> {props.children} </button> )) class Parent extends React.Component { constructor(props) { super(props); this.textInput = React.createRef(); } componentDidMount() { console.log(this.textInput.current) } render() { return ( <MyFunctionComponent ref={this.textInput} /> ); } }
第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
useImperativeHandle
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。useImperativeHandle 应当与 forwardRef 一起使用:
const MyFunctionComponent = React.forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return ( <input ref={inputRef} /> ) }) class Parent extends React.Component { constructor(props) { super(props); this.textInput = React.createRef(); } componentDidMount() { this.textInput.current.focus() } render() { return ( <MyFunctionComponent ref={this.textInput} /> ); } }
使用useImperativeHandle时父组件第一次没拿到子组件方法
背景需求
一个tab两个按钮A、B,默认选中的A,当点击到B时需要显示B对应的图表。考虑到B的图表在页面加载时已经初始化完成,所以点击B时再调用图表的resize方法。由于tab中的图表是写在子组件里,所以通过useImperativeHandle实现父组件调用子组件方法,React版本"react": "^18.1.0",代码如下
父组件:
const childRef = useRef() const item = [{ name: 'XXXX', content: <RunningRecord cRef={childRef} />, handClick: childRef.current?.resizeChart }] return <> …… <li onClick={() => { setTimeout(() => { console.log('~~item.handClick',item.handClick) item.handClick?.() }, 200) }} key={item.name}> {item.name} </li> …… <RunningRecord cRef={childRef} /> </>
子组件:
function RunningRecord({ cRef }) { …… useImperativeHandle(cRef,()=>({ resizeChart:()=> {dosomething……} }))
问题
这样写在本地开发模式中正常运行,但生产环境中父组件首次加载不能拿到子组件的方法,需tab切换到A再次且到B才行。猜想原因,大概在生产环境中,父组件把子组件暴露出来的方法绑定到UI中的点击事件中,而子组件初始化的时机晚,初始完成后并没有把事件传回来。
这个猜想不一定准确,欢迎知道的小伙伴们补充。
解决方法
在父组件中,将子组件赋值的过程放在useEffect中,不写依赖项参数(不是没有依赖的空数组),再运行,一切正常。
const usageRecordData = [{ name: 'XXXX', content: <RunningRecord cRef={childRef} />, }] useEffect(() => { usageRecordData[1].handClick = childRef.current?.resizeChart })
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。