三种使用方式

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 原生方法。

forwardRef

注意在这个例子中,我们的 Input 组件使用的是类组件,Input 组件可以改为使用函数组件吗?

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

如果我们强行使用,React 会报错并提示我们用 forwardRef:

function Input() {return <input value='value' />
}class App extends React.Component {constructor(props) {super(props)this.inputRef = React.createRef();}render() {return (<div><Input ref={this.inputRef} value='value' /></div>)}
}

但是呢,对于“获取组件实例,调用实例方法”这个需求,即使使用 forwardRef 也做不到,借助 forwardRef 后,我们也就是跟类组件一样,可以在组件上使用 ref 属性,然后将 ref 绑定到具体的 DOM 元素或者 class 组件上,也就是我们常说的 Refs 转发。

Refs 转发

有的时候,我们开发一个组件,这个组件需要对组件使用者提供一个 ref 属性,用于让组件使用者获取具体的 DOM 元素,我们就需要进行 Refs 转发,我们通常的做法是:

// 类组件
class Child extends React.Component {render() {const {inputRef, ...rest} = this.props;// 3. 这里将 props 中的 inputRef 赋给 DOM 元素的 refreturn <input ref={inputRef} {...rest} placeholder="value" />}
}
// 函数组件
function Child(props) {const {inputRef, ...rest} = props;// 3. 这里将 props 中的 inputRef 赋给 DOM 元素的 refreturn <input ref={inputRef} {...rest} placeholder="value" />
}class Parent extends React.Component {constructor(props) {super(props)// 1. 创建 refsthis.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} />}
}

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. 创建 refsthis.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 系列

全目录地址:https://github.com/mqyqingfeng/Blog

讲解 React 源码、React API 背后的实现机制,React 最佳实践、React 的发展与历史等,预计 50 篇左右,欢迎关注

如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

React 之 Refs 的使用和 forwardRef 的源码解读相关推荐

  1. arcgis开发 多版本之间如何兼容_arcgis api 4.x for js 结合 react 入门开发系列初探篇(附源码下载)...

    你还在使用 JQuery 或者 Dojo 框架开发 arcgis api 4.x for js 吗?想试试模块化开发吗?随着前端技术的发展,arcgis api 4.x for js 也有了结合 re ...

  2. 视频教程-React 全家桶从入门到实战到源码-其他

    React 全家桶从入门到实战到源码 上市公司前端开发工程师,专注于 React 技术栈,对 React 全家桶从 react-router 路由到 Redux 状态管理工具再到 webpack 打包 ...

  3. react中context到底是如何传递的-源码分析

    react中使用context 基本要求就是 父组件中声明Parent.prototype.getChildContext 父组件中声明Parent.childContextType 子组件声明 Ch ...

  4. react源码解读 {createClass}

    对一个框架源码的解读,既有利于更深入地了解框架,使用上更得心应手,又可以学习到其中代码组织的思路,吸收其精华简洁的写法以便于日常工作上使用.下面我就挑选近年大热门react(15.3.1),从中剖析框 ...

  5. python组件的react实现_【React源码解读】- 组件的实现

    前言 react使用也有一段时间了,大家对这个框架褒奖有加,但是它究竟好在哪里呢? 让我们结合它的源码,探究一二!(当前源码为react16,读者要对react有一定的了解) 回到最初 根据react ...

  6. react封装函数_React-Router源码解读

    起因 目前负责的项目中有一个微信网页,用的是react技术栈.在该项目中增加了一个微信分享功能后,线上ios出现了问题,经排查,定位到了react的路由系统. 这次线上bug,让我决定,先从react ...

  7. React v16版本 源码解读

    1. 配置 React 源码本地调试环境) 使用 create-react-app 脚手架创建项目 npx create-react-app react-test 弹射 create-react-ap ...

  8. React源码解读之更新的创建

    React 的鲜活生命起源于 ReactDOM.render ,这个过程会为它的一生储备好很多必需品,我们顺着这个线索,一探婴儿般 React 应用诞生之初的悦然. 更新创建的操作我们总结为以下两种场 ...

  9. React源码解读之React Fiber

    开始之前,先讲一下该文章能帮你解决哪些问题? 开始之前,先讲一下该文章能帮你解决哪些问题? facebook为什么要使用重构React React Fiber是什么 React Fiber的核心算法 ...

最新文章

  1. matlab生成多组多维高斯分布数据
  2. 临时“尿检官”谈违规为孙杨检测兴奋剂:一头雾水卷入该事件
  3. 201312-1- 出现次数最多的数
  4. WP7 开发(九) WP7控件开发(六)-DeepZoom技术
  5. anaconda更新python版本mac_macos - 如何使用conda升级到Python 3.6?
  6. oracle财务软件凭证打印,金算盘财务软件后台数据库为Oracle
  7. cg word List 3
  8. 一个人成就的高低,努力程度只是标配
  9. flume正则拦截器
  10. 基于 Keras 用 LSTM 网络做时间序列预测
  11. 躲避校园网客户端的检测实现客户端移动热点开启
  12. 把html转换成word,怎么把html转换成word
  13. DVP MIPI-CSI 摄像头接口的区别
  14. 【安全攻略】Thinkphp5.0检测上传的文件是否包含木马
  15. 普渡大学计算机科学本科,美国普渡大学计算机科学CS本科申请条件及案例
  16. CAD图纸电子签名应用
  17. 闲居即兴 - 反卷诗篇
  18. 表格一分为二html,如何在excel表中的将一个格子一分为二
  19. paperswithcode 论文阅读与代码复现
  20. UNITY3D自学(六)-- unity视频播放的Quicktime问题

热门文章

  1. css3实现3D动画
  2. 原码_反码_补码_移码_阶码
  3. OpenCV图像处理--常用图像拼接方法
  4. 基于PHP的餐厅微信点餐支付小程序设计与实现(任务书+开题+lunwen+前后台源码)
  5. ios内购返回html,iOS内购掉单问题处理方法
  6. github高级搜索技巧_分享 | 一些 GitHub 的使用小技巧
  7. C++ JSON解析之jsoncpp库的使用
  8. 什么是框架?为什么要学框架?
  9. 【零基础学Python】Day2 Python基本语法
  10. 01-Postman断言-常用断言