2022-09-17React00
请注意,本文编写于 589 天前,最后修改于 589 天前,其中某些信息可能已经过时。

转发 Refs

Ref 转发是一种自动将 ref 通过组件传递给子组件的技术。对于应用程序中的大多数组件,这通常不是必需的。但是,它对某些组件很有用,特别是在可重用的组件库中。最常见的情况如下所述。

转发 refs 给 DOM 组件

考虑一个渲染原生 button DOM 元素的 FancyButton 组件:

function FancyButton(props) {
  return (
    <button className="FancyButton">
      {props.children}
    </button>
  );
}

React 组件隐藏他们的实现细节,包括它们的渲染输出。使用 FancyButton 的其他组件 通常不需要 获得 ref 到内部 button DOM 元素。这很好,因为它可以防止组件过多依赖彼此的 DOM 结构。

虽然这样的封装对于像 FeedStoryComment 这样的应用程序级组件是可取的, 对于像 FancyButtonMyTextInput 这样的可高度重用的 “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 一样。

以下是对上述示例中发生情况逐步的说明:

  1. 我们通过调用 React.createRef 创建一个 React ref 并将其分配给 ref 变量。
  2. 通过将 ref 变量传递给指定ref为 JSX 属性的 <FancyButton ref={ref}>
  3. React将ref传递给 forwardRef 中的 (props, ref) => ... 函数作为第二个参数。
  4. 我们将这个ref参数转发到指定ref为 JSX 属性的 <button ref = {ref}>
  5. 当附加 ref 时,ref.current 将指向 <button> DOM节点。

注意

第二个 ref 参数仅在使用 React.forwardRef 调用定义组件时才存在。常规函数或类组件不接收 ref 参数,而且 props 也不提供 ref

Ref 转发不限于 DOM 组件。您也可以将 refs 转发给类组件实例。

在高阶组件中转发 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} />;
  });
}

本文作者:毛超颖

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!