简介
ref 即 reference ,是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。
组件被调用时会新建一个该组件的实例,而 ref 就会指向这个实例。它可以是一个回调函数,这个回调函数会在组件被挂载后立即执行。
为了防止内存泄漏,当卸载一个组件的时候,组件里所有的 ref 都会变为 null。
目录
- 简介
- ref的创建
- 类组件
- 函数组件
- ref作为属性
- 类组件
- ref属性是一个字符串(已废弃,不建议使用)
- ref 属性是一个函数
- ref属性是一个ref对象
- 函数组件
- ref作用
- 受控组件与非受控组件
- 受控组件
- 非受控组件
- useRef
ref的创建
所谓 ref 对象的创建,就是通过 React.createRef 或者 React.useRef 来在组件中创建一个 ref 原始对象,用来获取dom元素。
{current:null , // current指向ref对象获取到的实际内容,可以是dom元素,组件实例,或者其它。
}
类组件
在类组件上通过createRef创建ref对象。
class Test extends React.Component{constructor(props){super(props);this.currentRef = React.createRef(null);}componentDidMount(){console.log(this.currentRef);}render= () => <div ref={ this.currentRef } >createRef test</div>
}
export default Test;

createRef 就是创建了一个对象,对象上的 current 属性,用于保存通过 ref 获取的 DOM 元素,组件实例等。
// react/src/ReactCreateRef.js
export function createRef() {const refObject = {current: null,}return refObject;
}
createRef 一般用于类组件创建 ref 对象,可以将 ref 对象绑定在类组件实例上,这样更方便后续操作 ref。
注意:不要在函数组件中使用 createRef,否则会造成 ref 对象内容丢失等情况。
函数组件
在函数组件中通过 useRef 来创建 ref 对象。
export default function Test() {const currentRef = React.useRef(null)React.useEffect(() => {console.log(currentRef.current)}, []);return <div ref={ currentRef } >useRef test</div>
}

useRef 底层逻辑是和 createRef 差不多,就是 ref 保存位置不相同。类组件有一个实例 instance 能够维护像 ref 这种信息,所以createRef是直接把ref对象存储在 instance 上的,而函数组件没有这种instance,如果在函数组件上使用createRef,那么每一次函数组件更新,所有变量都会重新声明,那么ref就会随之被重置,起不到保存的效果了,这就是函数组件为什么不能用 createRef 的原因。
为了解决这个问题,hooks 和函数组件对应的 fiber 对象建立起关联,将 useRef 产生的 ref 对象挂到函数组件对应的 fiber 上,函数组件每次执行,只要组件不被销毁,函数组件对应的 fiber 对象一直存在,所以 ref 等信息就会被保存下来。
ref作为属性
react 对 ref 的处理,主要表现在父子组件交互时,处理标签中的 ref 属性,以及转发 ref。
类组件
ref属性是一个字符串(已废弃,不建议使用)
在类组件中,用字符串 ref 标记一个 DOM 元素或一个类组件,在react 在底层逻辑会判断类型,如果是 DOM 元素,会把真实 DOM 绑定在组件 this.refs (组件实例下的 refs )属性上,如果是类组件,会把子组件的实例绑定在 this.refs 上。
class Children extends React.Component {render = () => <div>hello,world</div>
}export default class Test extends React.Component {componentDidMount() {console.log(this.refs) // }render = () => <div><div ref="currentDom">ref是字符串</div><Children ref="currentComInstance" /></div>
}

上述代码中,取值时可以用this.ref.currentDom和this.ref.currentComInstance取到相应的元素。
这种方式已经废弃了,原因有以下几点:
- string ref 不可组合,如果第三方库的父组件已经给子组件传递了 ref,那么我们就无法再在子组件上添加 ref
- 回调引用没有一个所有者,因此您可以随时编写它们
- 不适用于Flow之类的静态分析
- string ref 强制React跟踪当前正在执行的组件
ref 属性是一个函数
class Children extends React.Component {render = () => <div>hello,world</div>
}export default class Test extends React.Component {currentDom = nullcurrentComponentInstance = nullcomponentDidMount() {console.log(this.currentDom)console.log(this.currentComponentInstance)}render = () => <div><div ref={(node) => this.currentDom = node} >ref是函数</div><Children ref={(node) => this.currentComponentInstance = node} /></div>
}

如上述代码所示,当用一个函数来标记 ref 的时候,将作为 callback 形式,等到真实 DOM 创建阶段,执行 callback ,获取的 DOM 元素或组件实例,再以回调函数第一个参数形式返回。所以可以像所以在上述代码中,用组件实例下的属性 currentDom和 currentComponentInstance 来接收真实 DOM 和组件实例。
ref属性是一个ref对象
创建ref对象,如第一部分所述,使用createRef
class Children extends React.Component {render = () => <div>hello,world</div>
}
export default class Test extends React.Component {currentDom = React.createRef(null)currentComponentInstance = React.createRef(null)componentDidMount() {console.log(this.currentDom)console.log(this.currentComponentInstance)}render = () => <div><div ref={this.currentDom}>ref是对象</div><Children ref={this.currentComponentInstance} /></div>
}

以上三种方法都无法用在函数组件中,因为函数组件没有实例。
函数组件
不能在函数组件上直接使用 ref 属性,因为他们没有实例,这时需要用到forwardRef。
import React, { useEffect, useRef } from 'react';const Child = React.forwardRef((props, ref) => {return <input ref={ref}></input>
})const Test = () => {const childrenRef = useRef();useEffect(() => {console.log(childrenRef.current); // child input}, [])return <Child ref={childrenRef}></Child>
}
export default Test;

React.forwardRef返回的是一个react组件,接受的参数是一个render函数,render(props, ref)。这个函数的第二个参数会将接收到的ref作为返回组件的ref属性,这就实现了ref的转发。
ref作用
其实ref是不推荐使用的,因为使用ref后,一些情况下元素会脱离react的控制。
受控组件与非受控组件
受控组件
受控组件指的是,在表单控件(例如input等),其值是受到react管理和控制的,即仅需要定义一个state和setState函数,在用户输入时UI和值都可以实时更新。
import React, { useState } from 'react';function ControlledComponentExample() {const [inputValue, setInputValue] = useState('');const handleInputChange = (event) => {setInputValue(event.target.value);};return (<div><label htmlFor="input">Enter something: </label><inputtype="text"id="input"value={inputValue}onChange={handleInputChange}/><p>You typed: {inputValue}</p></div>);
}
上述代码流程为,当input中值发生改变时,触发handleInputChange函数,从而将event.target.value更新到inputValue上,state的更新触发了页面的重新渲染,所以页面也更新了。
受控组件的特点时,数据流是单向的,均由state变化而触发,所以不应该强制赋值。
非受控组件
非受控组件即通过ref直接取dom元素中的值,而不是通过state控制它。
import React, { useRef } from 'react';function UncontrolledComponentExample() {const inputRef = useRef(null);const handleButtonClick = () => {alert('Input value is: ' + inputRef.current.value);};return (<div><label htmlFor="uncontrolledInput">Enter something: </label><inputtype="text"id="uncontrolledInput"ref={inputRef}/><button onClick={handleButtonClick}>Get Input Value</button></div>);
}
点击按钮的时候,handleButtonClick函数中直接通过ref拿取input中的值,不通过react自身的state变换。
useRef
在函数组件中,useRef 返回一个可变的 ref 对象,返回的 ref 对象在函数组件的整个生命周期内保持不变。所以 useRef 可以很方便地保存任何可变值。
import { useState, useEffect, useRef } from "react";export default function Test() {const ref = useRef(false)const [a, setA] = useState(0);useEffect(() => {if (!ref.current) {ref.current = true} else {//do some}}, [a]);return ('');
}