本文为意译,翻译过程当中掺杂本人的理解,若有误导,请放弃继续阅读。javascript
原文地址:Forwarding Refshtml
Ref forwarding是一种将ref钩子自动传递给组件的子孙组件的技术。对于应用的大部分组件,这种技术并非那么的必要。然而,它对于个别的组件仍是特别地有用的,尤为是那些可复用组件的类库。下面的文档讲述的是这种技术的最多见的应用场景。java
假设咱们有一个叫FancyButton的组件,它负责在界面上渲染出一个原生的DOM元素-button:react
function FancyButton(props) {
return (
<button className="FancyButton"> {props.children} </button>
);
}
复制代码
通常意义来讲,React组件就是要隐藏它们的实现细节,包括本身的UI输出。而其余引用了<FancyButton>
的组件也不太可能想要获取ref,而后去访问<FancyButton>
内部的原生DOM元素button。在组件间相互引用的过程当中,尽可能地不要去依赖对方的DOM结构,这属于一种理想的使用场景。app
对于一些应用层级下的组件,好比<FeedStory>
和<Comment>
组件(原文档中,没有给出这两个组件的实现代码,咱们只能顾名思义了),这种封装性是咱们乐见其成的。可是,这种封装性对于达成某些“叶子”(级别的)组件(好比,<FancyButton>
和<MyTextInput>
)的高可复用性是十分的不方便的。由于在项目的大部分场景下,咱们每每是打算把这些“叶子”组件都看成真正的DOM节点button和input来使用的。这些场景多是管理元素的聚焦,文本选择或者动画相关的操做。对于这些场景,访问组件的真正DOM元素是在所不免的了。dom
Ref forwarding是组件一个可选的特征。一个组件一旦有了这个特征,它就能接受上层组件传递下来的ref,而后顺势将它传递给本身的子组件。ide
在下面的例子当中,<FancyButton>
经过React.forwardRef的赋能,它能够接收上层组件传递下来的ref,并将它传递给本身的子组件-一个原生的DOM元素button:函数
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton"> {props.children} </button>
));
// 假如你没有经过 React.createRef的赋能,在function component上你是不能够直接挂载ref属性的。
// 而如今你能够这么作了,并能访问到原生的DOM元素:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
复制代码
经过这种方式,使用了<FancyButton>
的组件就能经过挂载ref到<FancyButton>
组件的身上来访问到对应的底层的原生DOM元素了-就像直接访问这个DOM元素同样。动画
下面咱们逐步逐步地来解释一下上面所说的是如何发生的:ui
<FancyButton>
的ref属性进一步将这个React ref传递下去。(props, ref) => ...
。届时,ref将做为这个函数的第二个参数。(props, ref) => ...
组件的内部,咱们又将这个ref 传递给了做为UI输出一部分的<button ref={ref}>
组件。<button ref={ref}>
组件被真正地挂载到页面的时候,,咱们就能够在使用ref.current
来访问真正的DOM元素button了。注意,上面提到的第二个参数ref只有在你经过调用React.forwardRef()来定义组件的状况下才会存在。普通的function component和 class component是不会收到这个ref参数的。同时,ref也不是props的一个属性。
Ref forwarding技术不仅仅用于将ref传递到DOM component。它也适用于将ref传递到class component,以此你能够获取这个class component的实例引用。
当你在你的组件类库中引入了forwardRef,那么你就应该把这个引入看做一个breaking change,并给你的类库发布个major版本。这么说,是由于一旦你引入了这个特性,那你的类库将会表现得跟以往是不一样( 例如:what refs get assigned to, and what types are exported),这将会打破其余依赖于老版ref功能的类库和整个应用的正常功能。
咱们得有条件地使用React.forwardRef
,即便有这样的条件,咱们也推荐你能不用就不要用。理由是:React.forwardRef
会改变你类库的行为,而且会在用户升级React版本的时候打破用户应用的正常功能。
这种技术对于高阶组件来讲也是特别有用的。假设,咱们要实现一个打印props的高阶组件,以往咱们是这么写的:
function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />; } } return LogProps; } 复制代码
高阶组件logProps
将全部的props都照样传递给了WrappedComponent
,因此高阶组件的UI输出和WrappedComponent
的UI输出将会同样的。举个例子,咱们将会使用这个高阶组件来把咱们传递给<FancyButton>
的props答应出来。
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);
复制代码
上面的例子有一个要注意的地方是:refs实际上并无被传递下去(到WrappedComponent
组件中)。这是由于ref并非真正的prop。正如key同样,它们都不是真正的prop,而是被用于React的内部实现。像上面的例子那样给一个高阶组件直接传递ref,那么这个ref指向的将会是(高阶组件所返回)的containercomponent实例而不是wrapper component实例:
import FancyButton from './FancyButton';
const ref = React.createRef();
// The FancyButton component we imported is the LogProps HOC.
// Even though the rendered output will be the same,
// Our ref will point to LogProps instead of the inner FancyButton component!
// This means we can't call e.g. ref.current.focus()
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>;
复制代码
幸运的是,咱们能够经过调用React.forwardRef这个API来显式地传递ref到FancyButton
组件的内部。React.forwardRef接收一个render function,这个render function将会获得两个实参:props和ref。举例以下:
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;
// Assign the custom prop "forwardedRef" as a ref
+ return <Component ref={forwardedRef} {...rest} />;
- return <Component {...this.props} />;
}
}
// Note the second param "ref" provided by React.forwardRef.
// We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
// And it can then be attached to the Component.
+ return React.forwardRef((props, ref) => {
+ return <LogProps {...props} forwardedRef={ref} />;
+ });
}
复制代码
React.forwardRef接收一个render function。React DevTools将会使用这个function来决定将ref forwarding component名显示成什么样子。
举个例子,下面的WrappedComponent就是ref forwarding component。它在React DevTools将会显示成“ForwardRef”:
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />; }); 复制代码
假如你给render function命名了,那么React DevTools将会把这个名字包含在ref forwarding component名中(以下,显示为“ForwardRef(myFunction)”):
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />; } ); 复制代码
你甚至能够把wrappedComponent的名字也囊括进来,让它成为render function的displayName的一部分(以下,显示为“ForwardRef(logProps(${wrappedComponent.name}))”):
function logProps(Component) {
class LogProps extends React.Component {
// ...
}
function forwardRef(props, ref) {
return <LogProps {...props} forwardedRef={ref} />; } // Give this component a more helpful display name in DevTools. // e.g. "ForwardRef(logProps(MyComponent))" const name = Component.displayName || Component.name; forwardRef.displayName = `logProps(${name})`; return React.forwardRef(forwardRef); } 复制代码
这样一来,你就能够看到一条清晰的refs传递路径:React.forwardRef -> logProps -> wrappedComponent。若是这个wrappeedComponent是咱们上面用React.forwardRef包裹的FancyButton
,这条路径能够更长:React.forwardRef -> logProps -> React.forwardRef -> FancyButton -> button。