React Refs 的使用forwardRef 源码示例解析

目录
  • 三种使用方式
    • 1. String Refs
    • 2. 回调 Refs
    • 3. createRef
  • 两种使用目的
  • Refs 转发
  • createRef 源码
  • forwardRef 源码

三种使用方式

React 提供了 Refs,帮助我们访问 DOM 节点或在 render 方法中创建的 React 元素。

React 提供了三种使用 Ref 的方式:

1. String Refs

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        setTimeout(() => {
             // 2. 通过 this.refs.xxx 获取 DOM 节点
             this.refs.textInput.value = 'new value'
        }, 2000)
    }
    render() {
        // 1. ref 直接传入一个字符串
        return (
            <div>
              <input ref="textInput" value='value' />
            </div>
        )
    }
}
root.render(<App />);

2. 回调 Refs

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        setTimeout(() => {
              // 2. 通过实例属性获取 DOM 节点
              this.textInput.value = 'new value'
        }, 2000)
    }
    render() {
        // 1. ref 传入一个回调函数
        // 该函数中接受 React 组件实例或 DOM 元素作为参数
        // 我们通常会将其存储到具体的实例属性(this.textInput)
        return (
            <div>
              <input ref={(element) => {
                this.textInput = element;
              }} value='value' />
            </div>
        )
    }
}
root.render(<App />);

3. createRef

class App extends React.Component {
    constructor(props) {
        super(props)
        // 1. 使用 createRef 创建 Refs
        // 并将 Refs 分配给实例属性 textInputRef,以便在整个组件中引用
        this.textInputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 3. 通过 Refs 的 current 属性进行引用
            this.textInputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 通过 ref 属性附加到 React 元素
        return (
            <div>
              <input ref={this.textInputRef} value='value' />
            </div>
        )
    }
}

这是最被推荐使用的方式。

两种使用目的

Refs 除了用于获取具体的 DOM 节点外,也可以获取 Class 组件的实例,当获取到实例后,可以调用其中的方法,从而强制执行,比如动画之类的效果。

我们举一个获取组件实例的例子:

class Input extends React.Component {
    constructor(props) {
        super(props)
        this.textInputRef = React.createRef();
    }
    handleFocus() {
        this.textInputRef.current.focus();
    }
    render() {
        return <input ref={this.textInputRef} value='value' />
    }
}
class App extends React.Component {
    constructor(props) {
        super(props)
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
                this.inputRef.current.handleFocus()
        }, 2000)
    }
    render() {
        return (
            <div>
              <Input ref={this.inputRef} value='value' />
            </div>
        )
    }
}

在这个例子中,我们通过 this.inputRef.current 获取到 Input 组件的实例,并调用了实例的 handleFocus 方法,在这个方法中,又通过 Refs 获取到具体的 DOM 元素,执行了 focus 原生方法。

Refs 转发

有的时候,我们开发一个组件,这个组件需要对组件使用者提供一个 ref 属性,用于让组件使用者获取具体的 DOM 元素,我们就需要进行 Refs 转发,这对于 class 组件并不是一个问题,举个示例代码:

class Child extends React.Component {
    render() {
        const {inputRef, ...rest} = this.props;
        // 3. 这里将 props 中的 inputRef 赋给 DOM 元素的 ref
        return <input ref={inputRef} {...rest} placeholder="value" />
    }
}
class Parent extends React.Component {
    constructor(props) {
        super(props)
        // 1. 创建 refs
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 4. 使用 this.inputRef.current 获取子组件中渲染的 DOM 节点
            this.inputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 因为 ref 属性不能通过 this.props 获取,所以这里换了一个属性名
        return <Child inputRef={this.inputRef} />
    }
}

但对于函数式组件,这却是一个问题。

我们是不能在函数组件上使用 ref 属性的,因为函数组件没有实例。

所以 React 提供了 forwardRef 这个 API,我们直接看使用示例:

// 3. 子组件通过 forwardRef 获取 ref,并通过 ref 属性绑定 React 元素
const Child = forwardRef((props, ref) => (
  <input ref={ref} placeholder="value" />
));
class Parent extends React.Component {
    constructor(props) {
        super(props)
        // 1. 创建 refs
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 4. 使用 this.inputRef.current 获取子组件中渲染的 DOM 节点
            this.inputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 传给子组件的 ref 属性
        return <Child ref={this.inputRef} />
    }
}

尤其是在我们编写高阶组件的时候,往往要实现 refs 转发。我们知道,一个高阶组件,会接受一个组件,返回一个包裹后的新组件,从而实现某种功能的增强。

但也正是如此,我们添加 ref,获取的会是包裹后的新组件的实例,而非被包裹的组件实例,这就可能会导致一些问题。

createRef 源码

现在我们看下 createRef 的源码,源码的位置/packages/react/src/ReactCreateRef.js,代码其实很简单,就只是返回了一个具有 current 属性的对象:

// 简化后
export function createRef() {
  const refObject = {
    current: null,
  };
  return refObject;
}

在渲染的过程中,refObject.current 会被赋予具体的值。

forwardRef 源码

那 forwardRef 源码呢?源码的位置/packages/react/src/ReactForwardRef.js,代码也很简单:

// 简化后
const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
export function forwardRef(render) {
  const elementType = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
  return elementType;
}

但是要注意这里的 $$typeof,尽管这里是 REACT_FORWARD_REF_TYPE,但最终创建的 React 元素的 $$typeof 依然为 REACT_ELEMENT_TYPE

关于 createElement 的源码分析参考 《React 之 createElement 源码解读》,我们这里简单分析一下,以 InputComponent 为例:

// 使用 forwardRef
const InputComponent = forwardRef(({value}, ref) => (
  <input ref={ref} className="FancyButton" value={value} />
));
// 根据 forwardRef 的源码,最终返回的对象格式为:
const InputComponent = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
}
// 使用组件
const result = <InputComponent />
// Bable 将其转译为:
const result = React.createElement(InputComponent, null);
// 最终返回的对象为:
const result = {
  $$typeof: REACT_ELEMENT_TYPE,
  type: {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  }
}

我们尝试着打印一下最终返回的对象,确实也是这样的结构:

React 系列

React 之 createElement 源码解读

React 之元素与组件的区别

以上就是React 之 Refs 的使用和 forwardRef 的源码解读的详细内容,更多关于React Refs使用forwardRef 的资料请关注我们其它相关文章!

(0)

相关推荐

  • react app rewrited替代品craco使用示例

    目录 1. 不使用custom-cra的原因 2. craco基本使用 3. 使用craco修改antd主题 4. 别名 5. babel扩展 6. 分包 7. 配置代理 8. 最后 1. 不使用custom-cra的原因 custom-cra,react-app-rewired 与 craco 都是用来无 eject 重写 CRA 配置 custom-cra上次更新在两年前,有些配置跟不上新的版本,例如使用webpack5配置less会出错, 虽说目前有了解决方案引入新包customize-c

  • concent渐进式重构react应用使用详解

    目录 正文 需求来了 准备工作 UI 实现 消灭生命周期函数 提升状态到store 解耦业务逻辑与UI 爱class,爱hook,让两者和谐共处 使用组件 结语 正文 传统的redux项目里,我们写在reducer里的状态一定是要打通到store的,我们一开始就要规划好state.reducer等定义,有没有什么方法,既能够快速享受ui与逻辑分离的福利,又不需要照本宣科的从条条框框开始呢?本文从普通的react写法开始,当你一个收到一个需求后,脑海里有了组件大致的接口定义,然后丝滑般的接入到co

  • React元素与组件的区别示例详解

    目录 从问题出发 元素与组件 元素 组件 问题如何解决 自定义内容 第一种实现方式 第二种实现方式 第三种实现方式 从问题出发 我被问过这样一个问题: 想要实现一个 useTitle 方法,具体使用示例如下: function Header() { const [Title, changeTitle] = useTitle(); return ( <div onClick={() => changeTitle('new title')}> <Title /> </div

  • React竞态条件Race Condition实例详解

    目录 竞态条件 React 与竞态条件 效果演示 问题复现 布尔值解决 useRequest 解决 Suspense 竞态条件 Race Condition,中文译为竞态条件,旨在描述一个系统或者进程的输出,依赖于不受控制事件的出现顺序或者出现时机. 举个简单的例子: if (x == 5) // The "Check" { y = x * 2; // The "Act" // 如果其他的线程在 "if (x == 5)" and "y

  • React Context 变迁及背后实现原理详解

    目录 Context 老的 Context API 基础示例 context 中断问题 解决方案 新的 Context API 基础示例 模拟实现 createContext 源码 Context 本篇我们讲 Context,Context 可以实现跨组件传递数据,大部分的时候并无需要,但有的时候,比如用户设置 了 UI 主题.地区偏好,如果从顶层一层层往下传反而有些麻烦,不如直接借助 Context 实现数据传递. 老的 Context API 基础示例 在讲最新的 API 前,我们先回顾下老

  • React Refs 的使用forwardRef 源码示例解析

    目录 三种使用方式 1. String Refs 2. 回调 Refs 3. createRef 两种使用目的 Refs 转发 createRef 源码 forwardRef 源码 三种使用方式 React 提供了 Refs,帮助我们访问 DOM 节点或在 render 方法中创建的 React 元素. React 提供了三种使用 Ref 的方式: 1. String Refs class App extends React.Component { constructor(props) { su

  • React Context原理深入理解源码示例分析

    目录 正文 一.概念 二.使用 2.1.React.createContext 2.2.Context.Provider 2.3.React.useContext 2.4.Example 三.原理分析 3.1.createContext 函数实现 3.2. JSX 编译 3.3.消费组件 - useContext 函数实现 3.4.Context.Provider 在 Fiber 架构下的实现机制 3.5.小结 四.注意事项 五.对比 useSelector 正文 在 React 中提供了一种「

  • Flink 侧流输出源码示例解析

    目录 Flink 侧流输出源码解析 源码解析 TimestampedCollector#collect CountingOutput#collect BroadcastingOutputCollector#collect RecordWriterOutput#collect ProcessOperator#ContextImpl#output CountingOutput#collect BroadcastingOutputCollector#collect RecordWriterOutput

  • JS前端操作 Cookie源码示例解析

    目录 引言 源码分析 使用 源码 分析 set get remove withAttributes & withConverter 总结 引言 前端操作Cookie的场景其实并不多见,Cookie也因为各种问题被逐渐淘汰,但是我们不用Cookie也可以学习一下它的思想,或者通过这次的源码来学习其他的一些知识. 今天带来的是:js-cookie 源码分析 使用 根据README,我们可以看到js-cookie的使用方式: // 设置 Cookies.set('name', 'value'); //

  • MyBatis SqlSource源码示例解析

    目录 正文 SqlNode SqlNode接口定义 BoundSql SqlSource SqlSource解析时机 SqlSource调用时机 总结 正文 MyBatis版本:3.5.12. 本篇讲从mybatis的角度分析SqlSource.在xml中sql可能是带?的预处理语句,也可能是带$或者动态标签的动态语句,也可能是这两者的混合语句. SqlSource设计的目标就是封装xml的crud节点,使得mybatis运行过程中可以直接通过SqlSource获取xml节点中解析后的SQL.

  • OpenMP task construct 实现原理及源码示例解析

    目录 前言 从编译器角度看 task construct Task Construct 源码分析 总结 前言 在本篇文章当中主要给大家介绍在 OpenMP 当中 task 的实现原理,以及他调用的相关的库函数的具体实现. 在本篇文章当中最重要的就是理解整个 OpenMP 的运行机制. 从编译器角度看 task construct 在本小节当中主要给大家分析一下编译器将 openmp 的 task construct 编译成什么样子,下面是一个 OpenMP 的 task 程序例子: #inclu

  • Flutter加载图片流程之ImageProvider源码示例解析

    目录 加载网络图片 ImageProvider resolve obtainKey resolveStreamForKey loadBuffer load(被废弃) evict 总结 困惑解答 加载网络图片 Image.network()是Flutter提供的一种从网络上加载图片的方法,它可以从指定的URL加载图片,并在加载完成后将其显示在应用程序中.本节内容,我们从源码出发,探讨下图片的加载流程. ImageProvider ImageProvider是Flutter中一个抽象类,它定义了一种

  • Python 装饰器常用的创建方式及源码示例解析

    目录 装饰器简介 基础通用装饰器 源码示例 执行结果 带参数装饰器 源码示例 源码结果 源码解析 多装饰器执行顺序 源码示例 执行结果 解析 类装饰器 源码示例 执行结果 解析 装饰器简介 装饰器(decorator)是一种高级Python语法.可以对一个函数.方法或者类进行加工.在Python中,我们有多种方法对函数和类进行加工,相对于其它方式,装饰器语法简单,代码可读性高.因此,装饰器在Python项目中有广泛的应用.修饰器经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理

  • Flutter加载图片流程之ImageCache源码示例解析

    目录 ImageCache _pendingImages._cache._liveImages maximumSize.currentSize clear evict _touch _checkCacheSize _trackLiveImage putIfAbsent clearLiveImages 答疑解惑 ImageCache const int _kDefaultSize = 1000; const int _kDefaultSizeBytes = 100 << 20; // 100 M

  • oracle数据与文本导入导出源码示例

    oracle提供了sqlldr的工具,有时需要讲数据导入到文本,oracle的spool可以轻松实现. 方便的实现oracle导出数据到txt.txt导入数据到oracle. 一.导出数据到txt 用all_objects表做测试 SQL> desc all_objects; Name Null? Type ----------------------------------------- -------- ---------------------------- OWNER NOT NULL

随机推荐