前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React.forwardRef的应用场景及源码解析

React.forwardRef的应用场景及源码解析

作者头像
进击的小进进
发布2020-02-24 12:34:19
2.1K0
发布2020-02-24 12:34:19
举报

一、React.forwardRef的应用场景

ref 的作用是获取实例,可能是 DOM 实例,也可能是 ClassComponent 的实例。

但会碰到以下问题: ① 如果目标组件是一个FunctionComponent的话,是没有实例的(PureComponent),此时用 ref 去传递会报错:

② 如果你是一个库的开发者的话,使用该库的人是不知道库的组件类别的,那么当库组件类别是FunctionComponent的时候,使用者想用 ref 获取库组件,怎么办?

③ redux 中的connect方法将组件包装成高阶组件(HOC),那么我如何通过 ref 去获取包装前的组件实例?

④ props 不能传递 ref

React 官方也表述了 ref 的使用条件:

React.forwardRef存在的意义就是为了解决以上问题。

关于React.forwardRef的使用,请参考:

https://zh-hans.reactjs.org/docs/react-api.html#reactforwardref

接下来我们讲下React.forwardRef的源码

二、React.forwardRef

作用: 创建一个 React 组件,该组件能够将其接收的 ref 属性转发到内部的一个组件中

源码:

代码语言:javascript
复制
export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  //删除了 dev 代码

  return {
    //被forwardRef包裹后,组件内部的$$typeof是REACT_FORWARD_REF_TYPE
    $$typeof: REACT_FORWARD_REF_TYPE,
    //render即包装的FunctionComponent,ClassComponent是不用forwardRef的
    render,
  };
}

解析: (1) 返回的是一个对象,即下面的变量 Child:

代码语言:javascript
复制
const Child = React.forwardRef((props, ref) => (
  <button ref={ref}>
    {props.children}
  </button>
));

console.log(Child,'Child29')

(2) 返回的对象里面的$$typeof,并不说明Child组件类型是REACT_FORWARD_REF_TYPE

我之前写的 React源码解析之React.createElement()和ReactElement() 中有提到ReactElementtypeREACT_ELEMENT_TYPE

代码语言:javascript
复制
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    //标识element的类型
    //因为jsx都是通过createElement创建的,所以ReactElement的类型固定:为REACT_ELEMENT_TYPE
    //重要!因为react最终渲染到DOM上时,需要判断$$typeof===REACT_ELEMENT_TYPE
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    //设置元素的内置属性
    type: type,
 
  };
}

注意: ① 变量Child是对象:

代码语言:javascript
复制
{
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };

Child组件是ReactElement对象,两者不一样

代码语言:javascript
复制
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
  };

当把Child转变为ReactElement的时候,Child对象作为Child组件的type属性:

代码语言:javascript
复制
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    //注意!!!
    type: {
      $$typeof: REACT_FORWARD_REF_TYPE,
      render,
    },
  };

不要认为被forwardRef包裹后,React 组件的$$typeof的值会改变成REACT_FORWARD_REF_TYPE

② 只有FunctionComponent才用得到forwardRefClassComponent `不需要

三、updateForwardRef

作用:

更新ref指向及被React.forwardRef包裹的FunctionComponent

源码:

代码语言:javascript
复制
//更新被React.forwardRef包裹的 FunctionComponent
function updateForwardRef(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderExpirationTime: ExpirationTime,
) {
  // TODO: current can be non-null here even if the component
  // hasn't yet mounted. This happens after the first render suspends.
  // We'll need to figure out if this is fine or can cause issues.

  //删除了 dev 代码

  //Component:{
  //   $$typeof: REACT_FORWARD_REF_TYPE,
  //   render,
  // }

  //FunctionComponent
  const render = Component.render;
  // 开发层面上不允许FunctionComponent,但你打印 props 的话是有的,
  // 因为是 React 只允许内部通过 props 传进来 ref
  const ref = workInProgress.ref;

  // The rest is a fork of updateFunctionComponent
  let nextChildren;
  //context 相关的可跳过
  prepareToReadContext(workInProgress, renderExpirationTime);
  prepareToReadEventComponents(workInProgress);
  if (__DEV__) {
    //删除了 dev 代码
  } else {
    //渲染的过程中,对里面用到的 hook函数做一些操作
    //关于renderWithHooks的讲解,请看:https://www.jianshu.com/p/959498695e83

    //注意:在updateFunctionComponent()中传的参数不是 ref,
    //而是 context:nextChildren = renderWithHooks(
    //   current,
    //   workInProgress,
    //   Component,
    //   nextProps,
    //   传的是 context 而不是 ref
    //   context,
    //   renderExpirationTime,
    // );
    nextChildren = renderWithHooks(
      current,
      workInProgress,
      render,
      nextProps,
      ref,
      renderExpirationTime,
    );
    //renderWithHooks 内部通过let children = Component(props, refOrContext)来更新 ref 或 context
  }

  //如果 props 相同,并且 ref 也相同的话,就不需要更新
  if (current !== null && !didReceiveUpdate) {
    //跳过hooks更新
    //关于bailoutHooks的讲解,请看:https://www.jianshu.com/p/959498695e83
    bailoutHooks(current, workInProgress, renderExpirationTime);
    //跳过该节点及所有子节点的更新
    //关于bailoutOnAlreadyFinishedWork的讲解,请看:https://www.jianshu.com/p/06b18db8b5d4
    return bailoutOnAlreadyFinishedWork(
      current,
      workInProgress,
      renderExpirationTime,
    );
  }

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  //将 ReactElement 变成 fiber对象,并更新,生成对应 DOM 的实例,并挂载到真正的 DOM 节点上
  //关于reconcileChildren的讲解,请看:https://www.jianshu.com/p/959498695e83
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

解析: (1) 逻辑比较简单,里面大部分函数在之前的文章里已解析过: ① 关于 renderWithHooksbailoutHooksreconcileChildren 的讲解,请看:

React源码解析之FunctionComponent(上) ② 关于 bailoutOnAlreadyFinishedWork 的讲解,请看:

React源码解析之workLoop

(2) ref 的更新是在renderWithHooks中:

代码语言:javascript
复制
let children = Component(props, refOrContext);

这里的Component是「二、React.forwardRef」中 Child 对象的render属性,也就是执行渲染FunctionComponent的方法

refOrContext在这里是workInProgress.ref

也就是说Component(props, refOrContext)的参数即React.forwardRef中的参数(props, ref)

代码语言:javascript
复制
React.forwardRef((props, ref) => (
  xxx
));

小进进还没开通留言功能,觉得不错的话,点「在看」、转发朋友圈都是一种支持 (●'◡'●)ノ

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、React.forwardRef的应用场景
  • 二、React.forwardRef
  • 三、updateForwardRef
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档