简介

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.jsexport 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.currentDomthis.ref.currentComInstance取到相应的元素。
这种方式已经废弃了,原因有以下几点:

  1. string ref 不可组合,如果第三方库的父组件已经给子组件传递了 ref,那么我们就无法再在子组件上添加 ref
  2. 回调引用没有一个所有者,因此您可以随时编写它们
  3. 不适用于Flow之类的静态分析
  4. 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 元素或组件实例,再以回调函数第一个参数形式返回。所以可以像所以在上述代码中,用组件实例下的属性 currentDomcurrentComponentInstance 来接收真实 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 ('');}