本篇从 React Refs 的使用场景、使用方式、注意事项,到 createRef
与 Hook useRef
的对比使用,最后以 React createRef
源码结束,剖析整个 React Refs,关于 React.forwardRef
会在下一篇文章深刻探讨。前端
React 的核心思想是每次对于界面 state 的改动,都会从新渲染整个Virtual DOM,而后新老的两个 Virtual DOM 树进行 diff(协调算法),对比出变化的地方,而后经过 render 渲染到实际的UI界面,node
使用 Refs 为咱们提供了一种绕过状态更新和从新渲染时访问元素的方法;这在某些用例中颇有用,但不该该做为 props
和 state
的替代方法。react
在项目开发中,若是咱们可使用 声明式 或 提高 state 所在的组件层级(状态提高) 的方法来更新组件,最好不要使用 refs。git
管理焦点(如文本选择)或处理表单数据: Refs 将管理文本框当前焦点选中,或文本框其它属性。github
在大多数状况下,咱们推荐使用受控组件来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的,每一个状态更新都编写数据处理函数。另外一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。要编写一个非受控组件,就须要使用 Refs 来从 DOM 节点中获取表单数据。算法
class NameForm extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
handleSubmit = (e) => {
console.log('A name was submitted: ' + this.input.current.value);
e.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
复制代码
由于非受控组件将真实数据储存在 DOM 节点中,因此再使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。若是你不介意代码美观性,而且但愿快速编写代码,使用非受控组件每每能够减小你的代码量。不然,你应该使用受控组件。数组
媒体播放: 基于 React 的音乐或视频播放器能够利用 Refs 来管理其当前状态(播放/暂停),或管理播放进度等。这些更新不须要进行状态管理。安全
触发强制动画: 若是要在元素上触发过强制动画时,可使用 Refs 来执行此操做。函数
集成第三方 DOM 库动画
Refs 有 三种实现:
createRef
是 **React v16.3 ** 新增的API,容许咱们访问 DOM 节点或在 render 方法中建立的 React 元素。
Refs 是使用 React.createRef()
建立的,并经过 ref
属性附加到 React 元素。
Refs 一般在 React 组件的构造函数中定义,或者做为函数组件顶层的变量定义,而后附加到 render()
函数中的元素。
export default class Hello extends React.Component {
constructor(props) {
super(props);
// 建立 ref 存储 textRef DOM 元素
this.textRef = React.createRef();
}
componentDidMount() {
// 注意:经过 "current" 取得 DOM 节点
// 直接使用原生 API 使 text 输入框得到焦点
this.textRef.current.focus();
}
render() {
// 把 <input> ref 关联到构造器里建立的 textRef 上
return <input ref={this.textRef} /> } } 复制代码
使用 React.createRef()
给组件建立了 Refs 对象。在上面的示例中,ref被命名 textRef
,而后将其附加到 <input>
DOM元素。
其中, textRef
的属性 current
指的是当前附加到 ref 的元素,并普遍用于访问和修改咱们的附加元素。事实上,若是咱们经过登陆 myRef
控制台进一步扩展咱们的示例,咱们将看到该 current
属性确实是惟一可用的属性:
componentDidMount = () => {
// myRef 仅仅有一个 current 属性
console.log(this.textRef);
// myRef.current
console.log(this.textRef.current);
// component 渲染完成后,使 text 输入框得到焦点
this.textRef.current.focus();
}
复制代码
在 componentDidMount
生命周期阶段,myRef.current
将按预期分配给咱们的 <input>
元素; componentDidMount
一般是使用 refs 处理一些初始设置的安全位置。
咱们不能在 componentWillMount
中更新 Refs,由于此时,组件还没渲染完成, Refs 还为 null
。
不一样于传递 createRef()
建立的 ref
属性,你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素做为参数,以使它们能在其余地方被存储和访问。
import React from 'react';
export default class Hello extends React.Component {
constructor(props) {
super(props);
this.textRef = null; // 建立 ref 为 null
}
componentDidMount() {
// 注意:这里没有使用 "current"
// 直接使用原生 API 使 text 输入框得到焦点
this.textRef.focus();
}
render() {
// 把 <input> ref 关联到构造器里建立的 textRef 上
return <input ref={node => this.textRef = node} /> } } 复制代码
React 将在组件挂载时将 DOM 元素传入ref
回调函数并调用,当卸载时传入 null
并调用它。在 componentDidMount
或 componentDidUpdate
触发前,React 会保证 refs 必定是最新的。
像上例, ref
回调函数是之内联函数的方式定义的,在更新过程当中它会被执行两次,第一次传入参数 null
,而后第二次会传入参数 DOM 元素。
这是由于在每次渲染时会建立一个新的函数实例,因此 React 清空旧的 ref 而且设置新的。咱们能够经过将 ref 的回调函数定义成 class 的绑定函数的方式能够避免上述问题,可是大多数状况下它是可有可无的。
export default class Hello extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// 经过 this.refs 调用
// 直接使用原生 API 使 text 输入框得到焦点
this.refs.textRef.focus();
}
render() {
// 把 <input> ref 关联到构造器里建立的 textRef 上
return <input ref='textRef' /> } } 复制代码
尽管字符串 stringRef 使用更方便,可是它有一些缺点,所以严格模式使用 stringRef 会报警告。官方推荐采用回调 Refs。
ref
属性被用于一个普通的 HTML 元素时,React.createRef()
将接收底层 DOM 元素做为它的 current
属性以建立 ref
,咱们能够经过 Refs 访问 DOM 元素属性。ref
属性被用于一个自定义 class 组件时,ref
对象将接收该组件已挂载的实例做为它的 current
,与 ref
用于 HTML 元素不一样的是,咱们可以经过 ref
访问该组件的props,state,方法以及它的整个原型 。stringRef
将会废弃(严格模式下使用会报警告),React.createRef()
API 是 React v16.3 引入的更新。useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的值。返回的 ref 对象在组件的整个生命周期内保持不变。
function Hello() {
const textRef = useRef(null)
const onButtonClick = () => {
// 注意:经过 "current" 取得 DOM 节点
textRef.current.focus();
};
return (
<>
<input ref={textRef} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
)
}
复制代码
####区别
useRef()
比 ref
属性更有用。useRef()
Hook 不只能够用于 DOM refs, useRef()
建立的 ref
对象是一个 current
属性可变且能够容纳任意值的通用容器,相似于一个 class 的实例属性。
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// ...
}
复制代码
这是由于它建立的是一个普通 Javascript 对象。而 useRef()
和自建一个 {current: ...}
对象的惟一区别是,useRef
会在每次渲染时返回同一个 ref 对象。
请记住,当 ref 对象内容发生变化时,useRef
并不会通知你。变动 .current
属性不会引起组件从新渲染。若是想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则须要使用回调 ref 来实现。
// ReactCreateRef.js 文件
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;
}
复制代码
其中 RefObject
为:
export type RefObject = {|
current: any,
|};
复制代码
这就是的 createRef 源码,实现很简单,但具体的它如何使用,如何挂载,将在后面的 React 渲染中介绍,敬请期待。
想看更过系列文章,点击前往 github 博客主页
走在最后,欢迎关注:前端瓶子君,每日更新