简介
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 ('');
}