前言
Ref
转发,通俗说就是通过forwardRef
这个API,将 ref 自动地从父组件传递给子组件的一种技巧。这种技术并不常见,使用场景主要有:
- 转发
ref
到组件内部的DOM
节点上 - 在高阶组件中转发
ref
上栗子
我们先写一个FancyButton
组件:
const FancyButton = (props) => {
return (
<input style={{width: 210}} type="text"/>
<button>{props.children}</button>
);
}
在父组件App
中渲染FancyButton
:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
const App = () => {
return (
<div>
<FancyButton>点我</FancyButton>
<button>获取焦点</button>
</div>
);
}
ReactDOM.render(<App />, root);
现在我们希望,点击「获取焦点」,使FancyButton
组件中的input
聚焦,该如何解决?你可能会想到通过props
将ref
属性从父组件传递给FancyButton
组件,再绑定input
,从而实现在父组件中操作子组件的 DOM节点。但是很遗憾,常规函数和 class 组件不接收ref
参数,且props
中也不存在ref
。
该forwardRef
出场了。
React.forwardRef
React.forwardRef(render)
的返回值是react
组件,接收的参数是一个render
函数,函数签名为render(props, ref)
,第二个参数将其接收的 ref 属性转发到render
返回的组件中。
const FancyButton = React.forwardRef((props, ref) => {
return (
<div>
<input ref={ref} style={{width: 210}} type="text"/>
<button>{props.children}</button></button>
</div>
);
});
这样,使用了FancyButton
的组件就可以获取到底层DOM节点input
的 ref,并在必要时进行访问。 完整代码如下:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {useRef} from 'react';
// 实现 ref 转发
const FancyButton = React.forwardRef((props, ref) => {
return (
<div>
<input ref={ref} style={{width: 210}} type="text"/>
<button>{props.children}</button></button>
</div>
)
})
const App = () => {
const ref = useRef();
// 在父组件中使用子组件的 ref
const handleClick = () => {
ref.current?.focus();
}
return (
<div>
<FancyButton ref={ref}>点我</FancyButton>
<button onClick={handleClick}>获取焦点</button>
</div>
);
}
ReactDOM.render(<App />, root);
还没完!React.forwardRef
通常配合useImperativeHandle
使用。
useImperativeHandle
useImperativeHandle
可以让我们在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用ref
这样的命令式代码。useImperativeHandle
应当与 forwardRef 一起使用。
useImperativeHandle(ref, createHandle, [deps])
- ref:定义 current 对象的 ref
- createHandle:一个函数,返回值是一个对象,即这个 ref 的 current
- [deps]:依赖列表,当监听的依赖发生变化时,useImperativeHandle 才会重新将子组件的实例属性输出到父组件
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {useRef, useImperativeHandle} from 'react';
const FancyInput = React.forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
}
}));
return <input ref={inputRef} type="text"/>
});
const App = () => {
const fancyInputRef = useRef();
return (
<div>
<FancyInput ref={fancyInputRef}/>
<button
onClick={() => fancyInputRef.current?.focus()}
>父组件调用子组件的 focus,实现 input 聚焦</button>
</div>
)
}
ReactDOM.render(<App />, root);
上面这个栗子中与直接转发ref
不同,直接转发ref
是将React.forwardRef
中 render 函数里的ref
参数直接应用在了返回元素的ref
属性上,父、子组件其实引用的是同一个ref
的current
对象,官方不建议使用这样的ref
透传。
而使用useImperativeHandle
后,可以让父、子组件分别有自己的ref
,通过React.forwardRef
将父组件的ref
透传过来,用useImperativeHandle
方法来自定义开放给父组件的current
。
最后
如果文中有错误或者不足之处,欢迎大家在评论区指正。
你的点赞是对我莫大的鼓励!感谢阅读~