Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

Refs 使用场景

在某些情况下,我们需要在典型数据流之外强制修改子组件,被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素,例如:

  • 管理焦点,文本选择或媒体播放。

  • 触发强制动画。

  • 集成第三方 DOM 库。

设置 Refs

1. createRef

支持在函数组件和类组件内部使用

createRef 是 React16.3 版本中引入的。

创建 Refs

使用 React.createRef() 创建 Refs,并通过 ref 属性附加至 React 元素上。通常在构造函数中,将 Refs 分配给实例属性,以便在整个组件中引用。

访问 Refs

ref 被传递给 render 中的元素时,对该节点的引用可以在 refcurrent 属性中访问。

import React from 'react';
export default class MyInput extends React.Component {constructor(props) {super(props);//分配给实例属性this.inputRef = React.createRef(null);}componentDidMount() {//通过 this.inputRef.current 获取对该节点的引用this.inputRef && this.inputRef.current.focus();}render() {//把 <input> ref 关联到构造函数中创建的 `inputRef` 上return (<input type="text" ref={this.inputRef}/>)}
}

ref 的值根据节点的类型而有所不同:

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。

  • ref 属性用于自定义的 class 组件时, ref 对象接收组件的挂载实例作为其 current 属性。

  • 不能在函数组件上使用 `ref` 属性,因为函数组件没有实例。

总结:为 DOM 添加 ref,那么我们就可以通过 ref 获取到对该DOM节点的引用。而给React组件添加 ref,那么我们可以通过 ref 获取到该组件的实例【不能在函数组件上使用 ref 属性,因为函数组件没有实例】。

2. useRef

仅限于在函数组件内使用

useRef 是 React16.8 中引入的,只能在函数组件中使用。

创建 Refs

使用 React.useRef() 创建 Refs,并通过 ref 属性附加至 React 元素上。

const refContainer = useRef(initialValue);

useRef 返回的 ref 对象在组件的整个生命周期内保持不变

访问 Refs

ref 被传递给 React 元素时,对该节点的引用可以在 refcurrent 属性中访问。

import React from 'react';export default function MyInput(props) {const inputRef = React.useRef(null);React.useEffect(() => {inputRef.current.focus();});return (<input type="text" ref={inputRef} />)
}

关于 React.useRef() 返回的 ref 对象在组件的整个生命周期内保持不变,我们来和 React.createRef() 来做一个对比,代码如下:

import React, { useRef, useEffect, createRef, useState } from 'react';
function MyInput() {let [count, setCount] = useState(0);const myRef = createRef(null);const inputRef = useRef(null);//仅执行一次useEffect(() => {inputRef.current.focus();window.myRef = myRef;window.inputRef = inputRef;}, []);useEffect(() => {//除了第一次为true, 其它每次都是 false 【createRef】console.log('myRef === window.myRef', myRef === window.myRef);//始终为true 【useRef】console.log('inputRef === window.inputRef', inputRef === window.inputRef);})return (<><input type="text" ref={inputRef}/><button onClick={() => setCount(count+1)}>{count}</button></>)
}

3. 回调 Refs

支持在函数组件和类组件内部使用

React 支持 回调 refs 的方式设置 Refs。这种方式可以帮助我们更精细的控制何时 Refs 被设置和解除。

使用 回调 refs 需要将回调函数传递给 React元素ref 属性。这个函数接受 React 组件实例 或 HTML DOM 元素作为参数,将其挂载到实例属性上,如下所示:

import React from 'react';export default class MyInput extends React.Component {constructor(props) {super(props);this.inputRef = null;this.setTextInputRef = (ele) => {this.inputRef = ele;}}componentDidMount() {this.inputRef && this.inputRef.focus();}render() {return (<input type="text" ref={this.setTextInputRef}/>)}
}

React 会在组件挂载时,调用 ref 回调函数并传入 DOM元素(或React实例),当卸载时调用它并传入 null。在 componentDidMountcomponentDidUpdate 触发前,React 会保证 Refs 一定是最新的。

可以在组件间传递回调形式的 refs.

import React from 'react';export default function Form() {let ref = null;React.useEffect(() => {//ref 即是 MyInput 中的 input 节点ref.focus();}, [ref]);return (<><MyInput inputRef={ele => ref = ele} />{/** other code */}</>)
}function MyInput (props) {return (<input type="text" ref={props.inputRef}/>)
}

4. 字符串 Refs(过时API)

函数组件内部不支持使用 字符串 refs [支持 createRef | useRef | 回调 Ref]

function MyInput() {return (<><input type='text' ref={'inputRef'} /></>)
}

类组件

通过 this.refs.XXX 获取 React 元素。

class MyInput extends React.Component {componentDidMount() {this.refs.inputRef.focus();}render() {return (<input type='text' ref={'inputRef'} />)}
}

Ref 传递

在 Hook 之前,高阶组件(HOC) 和 render props 是 React 中复用组件逻辑的主要手段。

尽管高阶组件的约定是将所有的 props 传递给被包装组件,但是 refs 是不会被传递的,事实上, ref 并不是一个 prop,和 key 一样,它由 React 专门处理。

这个问题可以通过 React.forwardRef (React 16.3中新增)来解决。在 React.forwardRef 之前,这个问题,我们可以通过给容器组件添加 forwardedRef (prop的名字自行确定,不过不能是 ref 或者是 key).

React.forwardRef 之前

import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';const withData = (WrappedComponent) => {class ProxyComponent extends React.Component {componentDidMount() {//code}//这里有个注意点就是使用时,我们需要知道这个组件是被包装之后的组件//将ref值传递给 forwardedRef 的 proprender() {const {forwardedRef, ...remainingProps} = this.props;return (<WrappedComponent ref={forwardedRef} {...remainingProps}/>)}}//指定 displayName.   未复制静态方法(重点不是为了讲 HOC)ProxyComponent.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';//复制非 React 静态方法hoistNonReactStatic(ProxyComponent, WrappedComponent);return ProxyComponent;
}

这个示例中,我们将 ref 的属性值通过 forwardedRefprop,传递给被包装的组件,使用:

class MyInput extends React.Component {render() {return (<input type="text" {...this.props} />)}
}MyInput = withData(MyInput);
function Form(props) {const inputRef = React.useRef(null);React.useEffect(() => {console.log(inputRef.current)})//我们在使用 MyInput 时,需要区分其是否是包装过的组件,以确定是指定 ref 还是 forwardedRefreturn (<MyInput forwardedRef={inputRef} />)
}

React.forwardRef

Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧,其允许某些组件接收 ref,并将其向下传递给子组件。

转发 ref 到DOM中:

import React from 'react';const MyInput = React.forwardRef((props, ref) => {return (<input type="text" ref={ref} {...props} />)
});
function Form() {const inputRef = React.useRef(null);React.useEffect(() => {console.log(inputRef.current);//input节点})return (<MyInput ref={inputRef} />)
}
  1. 调用 React.useRef 创建了一个 React ref 并将其赋值给 ref 变量。

  2. 指定 ref 为JSX属性,并向下传递

  3. React 传递 refforwardRef 内函数 (props, ref) =&gt; … 作为其第二个参数。

  4. 向下转发该 ref 参数到 ,将其指定为JSX属性

  5. ref 挂载完成,inputRef.current 指向 input DOM节点

注意

第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref

React.forwardRef 之前,我们如果想传递 ref 属性给子组件,需要区分出是否是被HOC包装之后的组件,对使用来说,造成了一定的不便。我们来使用 React.forwardRef 重构。

import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';function withData(WrappedComponent) {class ProxyComponent extends React.Component {componentDidMount() {//code}render() {const {forwardedRef, ...remainingProps} = this.props;return (<WrappedComponent ref={forwardedRef} {...remainingProps}/>)}}//我们在使用被withData包装过的组件时,只需要传 ref 即可const forwardRef = React.forwardRef((props, ref) => (<ProxyComponent {...props} forwardedRef={ref} />));//指定 displayName.forwardRef.displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';return hoistNonReactStatic(forwardRef, WrappedComponent);
}
class MyInput extends React.Component {render() {return (<input type="text" {...this.props} />)}
}
MyInput.getName = function() {console.log('name');
}
MyInput = withData(MyInput);
console.log(MyInput.getName); //测试静态方法拷贝是否正常function Form(props) {const inputRef = React.useRef(null);React.useEffect(() => {console.log(inputRef.current);//被包装组件MyInput})//在使用时,传递 ref 即可return (<MyInput ref={inputRef} />)
}

react-redux 中获取子组件(被包装的木偶组件)的实例

旧版本中(V4 / V5)

我们知道,connect 有四个参数,如果我们想要在父组件中子组件(木偶组件)的实例,那么需要设置第四个参数 optionswithReftrue。随后可以在父组件中通过容器组件实例的 getWrappedInstance() 方法获取到木偶组件(被包装的组件)的实例,如下所示:

//MyInput.js
import React from 'react';
import { connect } from 'react-redux';class MyInput extends React.Component {render() {return (<input type="text" />)}
}
export default connect(null, null, null, { withRef: true })(MyInput);
//index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import MyInput from './MyInput';function reducer(state, action) {return state;
}
const store = createStore(reducer);function Main() {let ref = React.createRef();React.useEffect(() => {console.log(ref.current.getWrappedInstance());})return (<Provider store={store}><MyInput ref={ref} /></Provider>)
}ReactDOM.render(<Main />, document.getElementById("root"));

这里需要注意的是:MyInput 必须是类组件,而函数组件没有实例,自然也无法通过 ref 获取其实例。react-redux 源码中,通过给被包装组件增加 ref 属性,getWrappedInstance 返回的是该实例 this.refs.wrappedInstance

if (withRef) {this.renderedElement = createElement(WrappedComponent, {...this.mergedProps,ref: 'wrappedInstance'})
}

新版本(V6 / V7)

react-redux新版本中使用了 React.forwardRef方法进行了 ref 转发。自 V6 版本起,option 中的 withRef 已废弃,如果想要获取被包装组件的实例,那么需要指定 connect 的第四个参数 optionforwardReftrue,具体可见下面的示例:

//MyInput.js 文件
import React from 'react';
import { connect } from 'react-redux';class MyInput extends React.Component {render() {return (<input type="text" />)}
}
export default connect(null, null, null, { forwardRef: true })(MyInput);

直接给被包装过的组件增加 ref,即可以获取到被包装组件的实例,如下所示:

//index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import MyInput from './MyInput';function reducer(state, action) {return state;
}
const store = createStore(reducer);function Main() {let ref = React.createRef();React.useEffect(() => {console.log(ref.current);})return (<Provider store={store}><MyInput ref={ref} /></Provider>)
}ReactDOM.render(<Main />, document.getElementById("root"));

同样,MyInput 必须是类组件,因为函数组件没有实例,自然也无法通过 ref 获取其实例。

react-redux 中将 ref 转发至 Connect 组件中。通过 forwardedRef 传递给被包装组件 WrappedComponentref

if (forwardRef) {const forwarded = React.forwardRef(function forwardConnectRef(props,ref) {return <Connect {...props} forwardedRef={ref} />})forwarded.displayName = displayNameforwarded.WrappedComponent = WrappedComponentreturn hoistStatics(forwarded, WrappedComponent)
}//...
const { forwardedRef, ...wrapperProps } = props
const renderedWrappedComponent = useMemo(() => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,[forwardedRef, WrappedComponent, actualChildProps]
)

ref 和 ReactDOM.findDOMNode(ref)

可以通过 ReactDOM.findDOMNode(ref) 来获取组件挂载后真正的 DOM 节点。

对于 HTML 元素,ref 引用的就是该元素的 DOM 节点,无需通过 ReactDOM.findDOMNode(ref) 来获取。

refReactDOM.findDOMNode(ref) 的区别

  • ref 添加在组件上,获取的是组件实例,添加到原生 HTML 上获取的是 DOM。

  • ReactDOM.findDOMNode(ref)ref 在 HTML 上,返回的是该 DOM;当 ref 在组件上时,返回的是该组件 render 方法中的 DOM。

参考链接:

  • Refs and the DOM

  • Refs 转发

  • Hook API 索引

你想知道的关于 Refs 的知识都在这了相关推荐

  1. 想要学习物联网工程方面的知识,我们应该学习些什么基础技术呢?

    最近几年,大学里开展了一门新的专业--物联网工程,它隶属于计算机系,但又与计算机科学与技术有所区别,物联网工程相对计科更强调硬件方面,想要学习物联网工程方面的知识,我们应该学习些什么基础技术呢? 想要 ...

  2. 所有的科学知识都是不确定的

    理查德·菲利普·费曼(Richard Phillips Feynman),1918年5月11日-1988年2月15日,美国著名理论物理学家,1965年,因在量子电动力学方面的成就而获得诺贝尔物理学奖. ...

  3. 费曼:所有科学知识都是不确定的

    来源 : 网络 作为科学家,我们知道伟大的进展都源于承认无知,源于思想的自由.那么这是我们的责任--宣扬思想自由的价值,教育人们不要惧怕质疑而应该欢迎它.讨论它,而且毫不妥协地坚持拥有这种自由--这是 ...

  4. 费曼:所有的科学知识都是不确定的

    编辑 ∑Gemini 来源:设计与哲学 一.不存在决定什么是好概念的权威  观察是一个概念是否含有真理的判官,但这个概念从何而来的呢?科学的快速进步和发展要求人类发明出一些东西用以检验. 在中世纪,人 ...

  5. html类选择器使用在什么场景,你需掌握的CSS知识都在这了(长文建议收藏,文末有福利)...

    1.CSS盒模型,在不同浏览器的差异 css 标准盒子模型 css盒子模型 又称为框模型(Box Model),包含了元素内容(content).内边距(padding).边框(border).外边距 ...

  6. css 商城 两列_你需掌握的CSS知识都在这了(长文建议收藏,文末有福利)

    1.CSS盒模型,在不同浏览器的差异 css 标准盒子模型 css盒子模型 又称为框模型(Box Model),包含了元素内容(content).内边距(padding).边框(border).外边距 ...

  7. 近视手术,是福音还是噩梦,知道这些危害,你还敢做吗,眼科小知识都在这里

    Hi,大家好,这是一篇非技术性文章,这篇主要想介绍激光近视手术的危害,让每个想做近视手术的朋友心里有个预期,当然这些问题发生的概率可能很小,但是落在你身上就是 100%,请看完之后在决定是否做手术. ...

  8. java培训基础知识都学哪些

    很多人都开始学习java技术,觉得java语言在未来的发展前景空间非常大,事实却是如此,那么针对于零基础的同学, 学习java技术需要学哪些呢?下面我们就来看看java培训基础知识都学哪些? java ...

  9. 想知道3D游戏建模师每天都在做什么吗?3D游戏建模的那些事

    想知道3D游戏建模师每天都在做什么吗?3D游戏建模的那些事 1.建模师的主要工作? 在游戏公司里,游戏建模主要分为3D场景建模和3D角色建模. 3D场景师的工作是根据原画设定及策划要求制作符合要求的3 ...

最新文章

  1. TCP/IP五层参考模型及其对应设备--运维笔记
  2. python字典get计数_Python内部是如何存储GC引用变量的计数的?
  3. python——前端常用的标签
  4. MFC 之 重绘按键Cbutton
  5. MapReduce的基本流程
  6. VS2005 解决应用程序配置不正确,程序无法启动问题(小问题,大思想)
  7. post如何获取到referrer_如何使用 ThinkJS 优雅的编写 RESTful API
  8. section和div有什么区别?
  9. windows核心编程第一章阅读
  10. “深度撞击”号探测器与地球失去联络
  11. vb3计算机,2014年计算机二级考试VB试题 3
  12. git 下载指定历史版本
  13. centos7配置ntp服务器
  14. win10您的windows许可证即将过期
  15. 2019最新activiti6.0工作流搭建平台
  16. Python 3.8 官网文档(中文版附下载)
  17. MySQL中varchar最大长度是多少(真正的官网解释,事实说话)
  18. 文献阅读 ——— Pre-train, Prompt, and Predict: A Systematic Survey of Prompting Methods in NLP
  19. QR分解、RQ分解与SVD
  20. Introduction to TitanEngine

热门文章

  1. http以及HttpServletRequest详解
  2. 前端面试超全整理0(html css)
  3. 微分方程matlab源代码,matlab 常微分方程数值解法 源程序代码
  4. k8s部署redis集群
  5. css 科技 边框_CSS3 边框
  6. C语言:十进制转换为二进制的数学方法
  7. ESXi生命周期政策
  8. (详解)矩阵快速幂详解与常见转移矩阵的构造
  9. mysql获取时间戳_mysql 获取当前时间戳
  10. STM32系列芯片命名含义一览