上一节:异常捕获边界
下一节:Fragments
Ref转发是一项自动从组件中将ref传递给其子组件的技术。但这在绝大部分组件中不是必需的。但是这对某些组件来说会非常有用,尤其是在某些可复用的第三方组件库中。常见的场景我们将在下面的内容描述。
我们来考虑下面的例子,FancyButton
组件渲染了一个原生button
DOM元素:
function FancyButton(props) {
return (
);
}
React组件隐藏了它们的实现细节,包括它们的渲染输出。使用FancyButton
的其他组件通常不需要获取它内部的button
DOM元素的ref。这是非常好的,因为它避免了组件过于依赖其他组件的DOM结构。
尽管这样封装代码对于应用层级的组件(比如FeedStory
或Comment
)来说是理想的,但这对类似FancyButton
或MyTextInput
这类的高复用性的“叶”组件来说会非常不方便。这些组件会在应用中充当类似原生DOM button
或input
来使用,因此对于管理焦点,选择或动画效果来说获取它们的DOM节点是不可避免的操作。
Refs转发是一个选择性加入的特性,它让组件接收它们收到的ref并将它传递(或称为转发)到更深层级的子元素中。
在下面的例子中,FancyButton
使用React.forwardRef
获取传递给它的ref,并转发给它渲染的button
元素。
const FancyButton = React.forwardRef((props, ref) => (
));
// 你现在可以直接获取DOM元素的引用了:
const ref = React.createRef();
Click me! ;
用这种方法,使用了FancyButton
组件的组件就得到了FancyButton组件中的button
元素的引用并且在必要时可以使用它——就像直接操纵DOM元素一样。
下面是对上面的例子代码运行的详细解释:
React.createRef
来创建一个React ref并将它赋值给ref
变量。
将ref
传递给FancyButton
。ref
作为第二个参数传递进forwardRef
内的(props, ref) => ...
函数。
将ref
转发给button
元素。ref.current
来获取
DOM节点。注意:
第二个参数ref只存在于当你调用React.forwardRef来定义组件时。正常的函数组件和class组件不会接收ref作为参数,也无法在props中获取到ref。
.
ref转发并不局限于在DOM元素上使用,你也可以将ref转发给一个class组件实例。
当你在组件库中使用forwardRef时,你应该把它当作一个破环性的更改并且发布一个新版本。因为你的组件库会有和以前的版本显著的不同(比如refs被分配给了谁,导出了声明类型),这可能会破坏使用者的应用和那些依赖老版本的组件。
处于同样的原因,当React,forwardRef存在时有条件地调用它是我们不推荐的:它改变了你的库的行为,并在用户更新React时会破坏用户的应用。
转发refs对高阶组件(也被称为HOC)十分有用。在下面的例子中我们使用高阶组件在控制台打印它接收的props:
function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return ;
}
}
return LogProps;
}
高阶组件logProps将所有的props
传递给它包裹的组件,所以渲染的结果将会是相同的。在下面的例子中,我们通过这个HOC将所有传递给“fancy button”组件的的props都打印出来:
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
//我们通过export LogProps来代替export FancyButton
//这依然会渲染FancyButton
export default logProps(FancyButton);
但在上面的例子中有一点需要注意:refs不会透传下去,因为ref
不是一个porp。就像key
一样,React处理它的方式是不同的。如果你为HOC添加了一个ref,那么这个ref将会成为最外面的容器组件的引用,而不是被包裹组件的。
这意味着原本打算给FancyButton
使用的ref实际上被绑定在了LogProps
组件上:
import FancyButton from './FancyButton';
const ref = React.createRef();
//我们引入的FancyButton组件实际上是LogProps高阶组件
//即使最终的渲染结果是相同的,
//我们的ref指向的是LogProps而不是内部的FancyButton组件!
//这意味着我们不能调用ref.current.focus()
;
但幸运的是,我们可以通过显式地调用React.forwardRef
API将refs转发给内部的FancyButton
组件。React.forwardRef
接收一个render函数(接收了props
和ref
作为参数)并返回一个React节点:
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
//将自定义的“forwardRef”最为ref
return ;
}
}
//注意React.forwardRef提供的第二个参数“ref”
//我们可以把它作为一个常规prop传递给LogProps,
//比如forwardRef。
//这个forwardRef prop之后将会被Component绑定
return React.forwardRef((props, ref) => {
return ;
});
}
React.forwardRef
接收一个render函数。React DevTools使用这个函数来决定为ref转发组件显示的内容。
比如下面的例子,组件将会在DevTools中显示“ForwardRef“:
const WrappedComponent = React.forwardRef((props, ref) => {
return ;
});
如果你为render函数起了名字,那么DevTools中显示的内容将会包含这个名字(比如:”ForwardRef(myFunction)”):
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return ;
}
);
你甚至可以通过设置函数的displayName
属性来使展示内容包括你包裹的组件:
function logProps(Component) {
class LogProps extends React.Component {
// ...
}
function forwardRef(props, ref) {
return ;
}
//给这个组件一个展示名称
//使其在DevTools中对用户更有帮助
//比如 "ForwardRef(logProps(MyComponent))"
const name = Component.displayName || Component.name;
forwardRef.displayName = `logProps(${name})`;
return React.forwardRef(forwardRef);
}
上一节:异常捕获边界
下一节:Fragments