转发 Refs
Ref 转发是一种自动将 ref 通过组件传递给子组件的技术。对于应用程序中的大多数组件,这通常不是必需的。但是,它对某些组件很有用,特别是在可重用的组件库中。最常见的情况如下所述。
转发 refs 给 DOM 组件
考虑一个渲染原生 button
DOM 元素的 FancyButton
组件:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
React 组件隐藏他们的实现细节,包括它们的渲染输出。使用 FancyButton
的其他组件 通常不需要 获得 ref 到内部 button
DOM 元素。这很好,因为它可以防止组件过多依赖彼此的 DOM 结构。
虽然这样的封装对于像 FeedStory
或 Comment
这样的应用程序级组件是可取的,
对于像 FancyButton
或 MyTextInput
这样的可高度重用的 “leaf” 组件来说可能会很不方便。
这些组件倾向于以与常规 DOM button
和 input
类似的方式在整个应用程序中使用,
并且访问他们的 DOM 节点可能不可避免地用于管理 焦点(focus),选择(selection)或动画(animations)。
Ref 转发是一种选择性加入的功能,可让某些组件接收他们收到的 ref
,并将其向下传递(换句话说,“转发”)给孩子。
在下面的例子中, FancyButton
使用 React.forwardRef
来获取传递给它的 ref
,
然后将其转发给它渲染的的 DOM button
:
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
通过这种方式,使用 FancyButton
的组件可以获得底层 button
DOM 节点的引用并在必要时访问它 -
就像他们直接使用 DOM button
一样。
以下是对上述示例中发生情况逐步的说明:
- 我们通过调用
React.createRef
创建一个 React ref 并将其分配给ref
变量。 - 通过将
ref
变量传递给指定ref
为 JSX 属性的<FancyButton ref={ref}>
。 - Reac t将
ref
传递给forwardRef
中的(props, ref) => ...
函数作为第二个参数。 - 我们将这个
ref
参数转发到指定ref
为 JSX 属性的<button ref = {ref}>
。 - 当附加 ref 时,
ref.current
将指向<button>
DOM节点。
注意
第二个
ref
参数仅在使用React.forwardRef
调用定义组件时才存在。常规函数或类组件不接收ref
参数,而且 props 也不提供 ref 。Ref 转发不限于 DOM 组件。您也可以将 refs 转发给类组件实例。
组件库维护者的注意事项
当你开始在组件库中使用 forwardRef
时,你应该将它视为一个重大变化,并发布库的一个新的主版本。这是因为你的库可能有明显不同的行为(例如什么 refs 被赋值,以及哪些类型被导出),这可能会破坏依赖于旧行为的应用和其他库。
出于同样的原因,也不建议有条件地应用 React.forwardRef
:它会改变您的图书馆的行为方式,并在升级 React 本身时破坏用户的应用程序。
在高阶组件中转发 refs
该技术对于 高阶组件(也称为HOC)也特别有用。 让我们从一个 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 <WrappedComponent {...this.props} />;
}
}
return LogProps;
}
“logProps” HOC 将所有 props
传递给它包裹的组件,
所以渲染输出将是相同的。
例如,我们可以使用此 HOC 记录所有传递给 “fancy button” 组件的 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 不会通过。那是因为 ref
不是 props(属性)。
像 key
一样,它的处理方式与 React 不同。
如果你添加一个 ref 到 HOC ,
这个 ref 将引用最外面的容器组件,而不是包裹的组件。
这意味着打算给 FancyButton
组件的 refs 实际上将被附加到 LogProps
组件:
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
接受一个渲染函数,该函数接收 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;
// Assign the custom prop "forwardedRef" as a ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// 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} />;
});
}
在DevTools中显示一个自定义名称
React.forwardRef
接受渲染函数。 React DevTools 使用此函数来确定要为 ref 转发组件显示什么内容。
例如,以下组件将在 DevTools 中显示为 ”ForwardRef” :
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
如果您命名渲染函数,DevTools 也将包含其名称(例如”ForwardRef(myFunction)”):
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
);
你甚至可以设置函数的 displayName
属性来包含你包裹的组件:
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);
}