[译]React高级话题之Forwarding Refs

栏目: IOS · Android · 发布时间: 5年前

内容简介:本文为意译,翻译过程中掺杂本人的理解,如有误导,请放弃继续阅读。原文地址:Forwarding RefsRef forwarding是一种将ref钩子自动传递给组件的子孙组件的技术。对于应用的大部分组件,这种技术并不是那么的必要。然而,它对于个别的组件还是特别地有用的,尤其是那些可复用组件的类库。下面的文档讲述的是这种技术的最常见的应用场景。

本文为意译,翻译过程中掺杂本人的理解,如有误导,请放弃继续阅读。

原文地址:Forwarding Refs

Ref forwarding是一种将ref钩子自动传递给组件的子孙组件的技术。对于应用的大部分组件,这种技术并不是那么的必要。然而,它对于个别的组件还是特别地有用的,尤其是那些可复用组件的类库。下面的文档讲述的是这种技术的最常见的应用场景。

正文

传递refs到DOM components

假设我们有一个叫FancyButton的组件,它负责在界面上渲染出一个原生的DOM元素-button:

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

一般意义来说,React组件就是要隐藏它们的实现细节,包括自己的UI输出。而其他引用了 <FancyButton> 的组件也不太可能想要获取ref,然后去访问 <FancyButton> 内部的原生DOM元素button。在组件间相互引用的过程中,尽量地不要去依赖对方的DOM结构,这属于一种理想的使用场景。

对于一些应用层级下的组件,比如 <FeedStory><Comment> 组件(原文档中,没有给出这两个组件的实现代码,我们只能顾名思义了),这种封装性是我们乐见其成的。但是,这种封装性对于达成某些“叶子”(级别的)组件(比如, <FancyButton><MyTextInput> )的高可复用性是十分的不方便的。因为在项目的大部分场景下,我们往往是打算把这些“叶子”组件都当作真正的DOM节点button和input来使用的。这些场景可能是管理元素的聚焦,文本选择或者动画相关的操作。对于这些场景,访问组件的真正DOM元素是在所难免的了。

Ref forwarding是组件一个可选的特征。一个组件一旦有了这个特征,它就能接受上层组件传递下来的ref,然后顺势将它传递给自己的子组件。

在下面的例子当中, <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元素一样。

下面我们逐步逐步地来解释一下上面所说的是如何发生的:

  1. 我们通过调用React.createRef来生成了一个React ref,并且把它赋值给了ref变量。
  2. 我们通过手动赋值给 <FancyButton> 的ref属性进一步将这个React ref传递下去。
  3. 接着,React又将ref传递给React.forwardRef()调用时传递进来的函数 (props, ref) => ... 。届时,ref将作为这个函数的第二个参数。
  4. (props, ref) => ... 组件的内部,我们又将这个ref 传递给了作为UI输出一部分的 <button ref={ref}> 组件。
  5. <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版本的时候打破用户应用的正常功能。

高阶组件里的Forwarding refs

这种技术对于高阶组件来说也是特别有用的。假设,我们要实现一个打印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}   />;
  + });
}
复制代码

在DevTools里面显示一个自定义的名字

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。


以上所述就是小编给大家介绍的《[译]React高级话题之Forwarding Refs》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

动画的时间掌握

动画的时间掌握

哈罗德•威特克、约翰•哈拉斯 / 中国电影出版社 / 2012-7-1 / 38.00元

动画的时间掌握(修订版),ISBN:9787106035334,作者:(英)哈罗德·威特克 等著一起来看看 《动画的时间掌握》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

html转js在线工具
html转js在线工具

html转js在线工具