[译]React高级指引5:Refs转发

原文连接:reactjs.org/docs/forwar…html

引言

Ref转发是一项自动从组件中将ref传递给其子组件的技术。但这在绝大部分组件中不是必需的。可是这对某些组件来讲会很是有用,尤为是在某些可复用的第三方组件库中。常见的场景咱们将在下面的内容描述。react

将refs转发给DOM组件

咱们来考虑下面的例子,FancyButton组件渲染了一个原生button DOM元素:数组

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

React组件隐藏了它们的实现细节,包括它们的渲染输出。使用FancyButton的其余组件一般不须要获取它内部的button DOM元素的ref。这是很是好的,由于它避免了组件过于依赖其余组件的DOM结构。bash

尽管这样封装代码对于应用层级的组件(好比FeedStoryComment)来讲是理想的,但这对相似FancyButtonMyTextInput这类的高复用性的“叶”组件来讲会很是不方便。这些组件会在应用中充当相似原生DOM buttoninput来使用,所以对于管理焦点,选择或动画效果来讲获取它们的DOM节点是不可避免的操做。app

Refs转发是一个选择性加入的特性,它让组件接收它们收到的ref并将它传递(或称为转发)到更深层级的子元素中。dom

在下面的例子中,FancyButton使用React.forwardRef获取传递给它的ref,并转发给它渲染的button元素。函数

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你如今能够直接获取DOM元素的引用了:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
复制代码

用这种方法,使用了FancyButton组件的组件就获得了FancyButton组件中的button元素的引用而且在必要时可使用它——就像直接操纵DOM元素同样。动画

下面是对上面的例子代码运行的详细解释:ui

  1. 咱们通多调用React.createRef来建立一个React ref并将它赋值给ref变量。
  2. 经过声明JSX属性<FancyButton ref={ref}>ref传递给FancyButton
  3. React将ref做为第二个参数传递进forwardRef内的(props, ref) => ...函数。
  4. 咱们经过声明JSX属性<button ref={ref}>ref转发给button元素。
  5. 当ref被绑定时,咱们就能够经过ref.current来获取<button>DOM节点。

注意: 第二个参数ref只存在于当你调用React.forwardRef来定义组件时。正常的函数组件和class组件不会接收ref做为参数,也没法在props中获取到ref。 . ref转发并不局限于在DOM元素上使用,你也能够将ref转发给一个class组件实例。this

组件库维护者的注意事项

当你在组件库中使用forwardRef时,你应该把它看成一个破环性的更改而且发布一个新版本。由于你的组件库会有和之前的版本显著的不一样(好比refs被分配给了谁,导出了声明类型),这可能会破坏使用者的应用和那些依赖老版本的组件。

处于一样的缘由,当React,forwardRef存在时有条件地调用它是咱们不推荐的:它改变了你的库的行为,并在用户更新React时会破坏用户的应用。

在高阶组件中转发refs

转发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 <WrappedComponent {...this.props} />;
    }
  }

  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()
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}
/>;
复制代码

但幸运的是,咱们能够经过显式地调用React.forwardRef API将refs转发给内部的FancyButton组件。React.forwardRef接收一个render函数(接收了propsref做为参数)并返回一个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 <Component ref={forwardedRef} {...rest} />;
    }
  }

  //注意React.forwardRef提供的第二个参数“ref”
  //咱们能够把它做为一个常规prop传递给LogProps,
  //好比forwardRef。
  //这个forwardRef prop以后将会被Component绑定
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}
复制代码

在DevTools中显式自定义名称

React.forwardRef接收一个render函数。React DevTools使用这个函数来决定为ref转发组件显示的内容。

好比下面的例子,组件将会在DevTools中显示“ForwardRef“:

const WrappedComponent = React.forwardRef((props, ref) => {
  return <LogProps {...props} forwardedRef={ref} />;
});
复制代码

若是你为render函数起了名字,那么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} />;
  }

  //给这个组件一个展现名称
  //使其在DevTools中对用户更有帮助
  //好比 "ForwardRef(logProps(MyComponent))"
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}
复制代码
相关文章
相关标签/搜索