Skip to content

10.引入forwardRef的底层逻辑

上一小节我们实现了类组件的ref相关代码,本小节我们来探索如何为函数式组件引入ref的相关能力。大家想一想,类组件、函数组件都是组件,为什么函数组件不能像类组件那样实现ref的相关功能,还要在这里单独用一小节来进行探索。

我们在实现类组件的ref相关功能的时候,之所以能将ref和类组件生成的DOM进行关联,是因为我们当时通过ref && (ref.current = instance);先将类组件的实例和ref进行关联,然后通过调用实例的方法间接操作。

在前面分析类组件的ref实现原理的小节,我们已经知道要让ref发挥作用可以通过以下3个步骤完成:

  1. 初始化:this.myRef = React.createRef(); 相当于 this.myRef =
  2. 传递ref引用:ref= { this.myRef },这里有个隐藏的动作,this.myRef.current = xxx dom
  3. 取值:const element = this.myRef.current;

如果我们企图在函数组件中,模仿类组件的这三个步骤来实现ref的相关功能是无法成功的,比如官方文档上提供的下面的代码:

js
// https://reactjs.org/docs/refs-and-the-dom.html
function MyFunctionComponent() {
  return <input />;
}
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // This will *not* work!
    return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }
}

为什么不能成功呢?

https://reactjs.org/docs/refs-and-the-dom.html 官方文档提到过:By default, you may not use the ref attribute on function components because they don’t have instances

官方文档说是因为函数组件没有实例,这个解释很正确,但对于很多同学来讲可能会感觉不知所云。我们先忽略这个解释,跟着我的思路往下走。

我们在本章前面介绍过,函数组件本质上就是一个函数,只不过这个函数的返回值是一个虚拟DOM。而这段代码中,我们在使用函数组件的时候,只是将this.textInput和MyFunctionComponent进行关联,但并没有将this.textInput和MyFunctionComponent的返回值进行关联。问题产生的原因已经很清晰,那解决方案也就很清晰了,那就是建立函数组件的ref值和函数组件对应函数的返回值之间的关联。那现在面临的问题就是怎么进行关联,能自然联想到的方式就是将ref作为函数组件的参数传入,并在返回值中进行手动使用,比如:

js
function MyFunctionComponent(props, functionRef) {
  return <input ref={functionRef}/>;
}

将react-dom.js中的getDomByFunctionComponent进行改造:

js
function getDomByFunctionComponent(vNode) {
    let { type, props, ref } = vNode;
    let renderVNode = type(props, ref);
    if (!renderVNode) return null;
    return createDOM(renderVNode);
}

但是这种方案有个弊端,就是每个函数组件的返回值都需要我们手动进行ref的关联,给日常开发带来了困扰,这种方案当然是不可行的,所以我们需要改进,将前面想到的手动关联的方式转化成自动关联。所以React提供了一个单独的api来实现这个关联的过程,这个api就是forwardRef,至于forwardRef具体如何实现我们会在下一小节进行介绍。。

基于 VitePress 构建