[译]React高级话题之Forwarding Refs

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

内容简介:本文为意译,翻译过程中掺杂本人的理解,如有误导,请放弃继续阅读。原文地址: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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Essential ActionScript 3.0

Essential ActionScript 3.0

Colin Moock / Adobe Dev Library / June 22, 2007 / $34.64

ActionScript 3.0 is a huge upgrade to Flash's programming language. The enhancements to ActionScript's performance, feature set, ease of use, cleanliness, and sophistication are considerable. Essentia......一起来看看 《Essential ActionScript 3.0》 这本书的介绍吧!

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

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具