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

 更新时间:2022年11月07日 09:12:01   作者:冴羽  
这篇文章主要为大家介绍了React 之 Refs 的使用和 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 的资料请关注脚本之家其它相关文章!

相关文章

  • react中实现修改input的defaultValue

    react中实现修改input的defaultValue

    这篇文章主要介绍了react中实现修改input的defaultValue方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • React Diffing 算法完整指南(示例详解)

    React Diffing 算法完整指南(示例详解)

    Diffing 算法是 React 用于比较两棵虚拟 DOM 树差异的算法,用来确定需要更新的部分,从而最小化 DOM 操作,这篇文章主要介绍了React Diffing 算法完整指南,需要的朋友可以参考下
    2024-12-12
  • react项目中redux的调试工具不起作用的解决

    react项目中redux的调试工具不起作用的解决

    这篇文章主要介绍了react项目中redux的调试工具不起作用的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • React 父子组件通信的实现方法

    React 父子组件通信的实现方法

    这篇文章主要介绍了React 父子组件通信的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • react-intl实现React国际化多语言的方法

    react-intl实现React国际化多语言的方法

    这篇文章主要介绍了react-intl实现React国际化多语言的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 想用好React的你必须要知道的一些事情

    想用好React的你必须要知道的一些事情

    现在最热门的前端框架,毫无疑问是 React 。下面这篇文章主要给大家分享了关于想用好React的你必须要知道的一些事情,文中介绍的非常详细,对大家具有一定参考学习价值,需要的朋友们下面来一起看看吧。
    2017-07-07
  • Component与PureComponent对比解析

    Component与PureComponent对比解析

    这篇文章主要为大家介绍了Component与PureComponent解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • React组件性能提升实现方法详解

    React组件性能提升实现方法详解

    这篇文章主要介绍了React组件性能最佳优化实践分享,React组件性能优化的核心是减少渲染真实DOM节点的频率,减少Virtual DOM比对的频率,更多相关内容需要的朋友可以参考一下
    2023-03-03
  • 高性能React开发React Server Components详解

    高性能React开发React Server Components详解

    ReactServerComponents通过服务器端渲染、自动代码分割等技术,实现了高性能的React开发,它解决了客户端数据请求链式延迟、敏感数据暴露风险等问题,提供了更好的用户体验和安全性,本文介绍高性能React开发React Server Components详解,感兴趣的朋友一起看看吧
    2025-03-03
  • React实现antdM的级联菜单实例

    React实现antdM的级联菜单实例

    这篇文章主要为大家介绍了React实现antdM的级联菜单实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10

最新评论