React16源码: createRef与forwardRef源码实现

ref 概述

  • 拿一个场景举例开始
    • 比如,在react当中写一个组件,类型是 class Component
    • 在 render function 里面渲染一系列的子组件或者 dom节点
    • 会有这样的需求,就是获取某个dom节点或某个子组件的实例来做一些手动的操作
    • 不局限于 props 更新这种方式来更新这个节点,获取节点后,可以绑定某些事件做一些操作
    • 如果没有其他好方法,可能要自己去写 document.querySelector之类的选择器来获取这个 dom 节点
  • 在 react 当中就为我们提供了这种方式
    • 通过 ref 这个特殊的 attribute 可以非常方便的在一个组件内部
    • 获取到它的一个子节点的实例对象, 这就是 ref 的一个核心功能
  • 在 react 当中有三种使用ref的方式
    • string ref
    • function
    • createRef

ref 应用示例

1 ) 演示代码

js 复制代码
import React from 'react'

export default class RefDemo extends React.Component {
  constructor() {
    super()
    this.objRef = React.createRef()
  }

  componentDidMount() {
    setTimeout(() => {
      this.refs.stringRef.textContent = 'string ref'
      this.methodRef.textContent = 'method ref'
      this.objRef.current.textContent = 'obj ref'
    }, 1000)
  }

  render() {
    return (
      <>
        <p ref="stringRef">xxxx</p>
        <p ref={ele => (this.methodRef = ele)}>yyyy</p>
        <p ref={this.objRef}>zzzz</p>
      </>
    )
  }
}

2 )代码说明

  • 第一种使用了 stringRef
    • 也就是我们在想要获取的那个节点的 props上面
    • 使用了一个 ref 属性, 然后传入一个字符串, react在完成这一个节点的渲染之后
    • 它会在 this.refs 这个对象上面挂载这个string对应的一个key
    • 这个key所指向的就是我们这个节点实例的对象
    • 如果是dom节点, 它就会对应dom的实例
    • 如果是子组件,就是子组件的实例,即:class Component
    • 如果是一个 function Component,正常来讲它是会失败的
    • 拿到的会是一个 undefined,因为 function Component 没有实例
    • 如果不让其出错,需要使用 forwardRef,后续来说
      * 此种方式不被推荐
  • 第二种使用了 function
    • 在 ref 属性上传入一个 method 方法,它接受一个参数
    • 这个参数就是一个节点对应的实例
      • 如果是dom节点对应的是dom实例
      • 如果是组件对应的是组件的实例
    • 然后在this上面去挂载某一个属性,比如上面的 methodRef, 基于其 textContent 属性来重新赋值
  • 第三种是通过 React.createRef 这个API
    • 在 class Component 内部,我们使用 this.objRef = React.createRef() 去创建了一个对象
      • 这个对象相当于 { current: null} , 默认值是 null
    • 把这个对象传给某一个节点的 ref 属性上,在组件渲染完成之后
    • 会把这个节点对应的实例挂载到这个对象的 current 这个属性上面
    • 调用它就是通过 this.objRef.current 操作它就可以了
  • 这个 demo 效果是不用跑也知道是怎样的一个变化
  • 以上三种情况的前两种没有在源码中有过多的体现,我们看下第三种情况 createRef

3 )源码探究

  • 从入口文件 React.js 中可见

    js 复制代码
    import {createRef} from './ReactCreateRef';
  • 定位到 ReactCreateRef.js 中

    js 复制代码
    /**
     * Copyright (c) Facebook, Inc. and its affiliates.
     *
     * This source code is licensed under the MIT license found in the
     * LICENSE file in the root directory of this source tree.
     * @flow
     */
    
    import type {RefObject} from 'shared/ReactTypes';
    
    // an immutable object with a single mutable value
    export function createRef(): RefObject {
      const refObject = {
        current: null,
      };
      if (__DEV__) {
        Object.seal(refObject);
      }
      return refObject;
    }
  • 这个代码看上去,非常简单,它返回的就是一个简单的对象

  • DEV 相关的判断不是核心,忽略即可

  • 它里面的属性 current, 默认值是 null

forward ref

1 )示例演示

  • 关于 forward ref, 先看一个示例

    js 复制代码
    import React from 'react'
    
    const TargetComponent = React.forwardRef((props, ref) => (
      <input type="text" ref={ref} />
    ))
    
    export default class Comp extends React.Component {
      constructor() {
        super()
        this.ref = React.createRef()
      }
    
      componentDidMount() {
        this.ref.current.value = 'ref input'
      }
    
      render() {
        return <TargetComponent ref={this.ref} />
      }
    }
    • 在这个文件下,定义了两个组件 Comp, TargetComponent
    • Comp 组件
      • 里面创建了一个ref, 并传递给 TargetComponent 组件
      • ref 是为了去获取某一个节点的实例的,通常会获取dom节点的实例
      • 有时,也会获取一下class Component 的实例
      • 如果组件是一个 纯的 function Component, 没有实例,则会出错
        • 如果我是一个组件的提供者,就是开源了一些组件
        • 用户(开发者) 一开始并不知道是否是有实例的组件
        • 还有就是,比如一些包, 如 Redux,提供的 connect 方法里的 HOC (高阶组件)
        • 作为用户想要获取自己写的组件的实例的时候,传入ref,获得的是被包装过的组件
      • 解决之道是外面套上 React.forwardRef,如上代码所示
        • forwardRef 可以帮助我们实现一个 ref 的传递
    • TargetComponent 组件
      • 就是我们基于 React.forwardRef 实现的 function Component
      • 如果要把它改造成HOC, 也可以基于此函数的回调将 ref 进行传递挂载
      • 这样就不会违背开发者的意图

2 )源码分析

定位到 forwardRef.js 中

js 复制代码
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import {REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';

import warningWithoutStack from 'shared/warningWithoutStack';

export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  if (__DEV__) {
    if (typeof render !== 'function') {
      warningWithoutStack(
        false,
        'forwardRef requires a render function but was given %s.',
        render === null ? 'null' : typeof render,
      );
    } else {
      warningWithoutStack(
        // Do not warn for 0 arguments because it could be due to usage of the 'arguments' object
        render.length === 0 || render.length === 2,
        'forwardRef render functions accept exactly two parameters: props and ref. %s',
        render.length === 1
          ? 'Did you forget to use the ref parameter?'
          : 'Any additional parameter will be undefined.',
      );
    }

    if (render != null) {
      warningWithoutStack(
        render.defaultProps == null && render.propTypes == null,
        'forwardRef render functions do not support propTypes or defaultProps. ' +
          'Did you accidentally pass a React component?',
      );
    }
  }

  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
}
  • 忽略 DEV 判断代码,其本质上就只有一个 return 对象

    js 复制代码
    return {
      $$typeof: REACT_FORWARD_REF_TYPE,
      render,
    };
    • $$typeoff,是 REACT_FORWARD_REF_TYPE
      • 注意
        • 使用 forwordRef 时, 用这个API最终创建出来的 ReactElement
        • 比如上面示例的 TargetComponent组件最终会被创建成一个 ReactElement,比如,别名叫 A
        • TargetComponent组件 就是 forwordRef 返回的对象,这个不难理解,因为代码如此
        • TargetComponent 对应的 组件A 的 type 是 TargetComponent
        • TargetComponent 对应的 组件A的 $$typeofREACT_ELEMENT_TYPE
          • A的 $$typeof 不是 REACT_FORWARD_REF_TYPE
          • 因为,所有通过React.createElement创建的节点的 $$typeof 都是 REACT_ELEMENT_TYPE
          • TargetComponent组件最终会被React.createElement创建出来
          • 这一点不要搞混了
    • render
      • 就是我们传进来的 function component
相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax