Refs提供了一个访问render()方法内DOM节点或者ReactNode的方法
典型的React数据流中,props是父组件和子组件交互的唯一手段。要修改一个子组件,就需要使用新的props重新渲染它。然而,确实存在少数一些情况需要命令性地(imperatively
)修改一个子节点而不是通过典型的props数据流方式。被修改的子节点可能是一个React组件实例(比如调用某个子组件的实例方法),亦或是一个DOM元素(比如手动地控制某个input标签聚焦)
。对于这两种情况,React都提供了各种处理方法,refs就是其中的一种。
1. refs适用的场景
- 手动控制DOM节点
- 获取子组件的尺寸或者实例方法
- 和第三方DOM库集成(典型的jPlayer)
注意:不要滥用refs,比如:在使用antd的<Modal />时,可以直接通过修改props.visible为true或false即可实现Modal组件的显示和隐藏,则大可不必使用该组件的show()、hide()方法
2. 创建Refs
注意:Ract 16.3引入的API React.createRef()。比较旧的React版本,保留了refs关键字。不管是新旧版本,都建议使用refs回调方式(最后的有对应的示例)。本文主要实现一个
页面加载时,Input组件自动聚焦,并且在点击Button组件时聚焦Input组件
的功能,使用的方法为React.createRef()。github代码库中有对应的新旧版本实现方式。
- refs通过React.createRef()创建,使属性ref附加到React元素上。Refs通常在
一个组件构造时赋值给一个实例属性
,这样在整个组件中他们都可以被引用到。 - 创建以后,通过React.createRef().current方法获取。
根据节点类型的不同,ref的值也不同:
- 如果ref用在HTML元素上,构造函数中通过React.createRef()创建的ref会将原生DOM元素放到它的current属性中。
- 如果ref用在自定义组件类型上,ref使用它的current属性指向所挂载的组件实例。
- 函数式组件上不能使用ref,因为它们没有实例。
3. DOM中创建与使用
class Input extends React.Component {
constructor(props) {
super(props)
// 在构造方法内初始化
this.inputRef = React.createRef()
}
componentDidMount() {
// 使用.current调用
this.inputRef.current.focus();
}
// Input的实例方法
focus = () => {
if(this.inputRef.current) this.inputRef.current.focus();
}
render() {
return (
<div className="block">
<p>Input 加载时自动聚焦</p>
<input ref={this.inputRef} />
</div>
)
}
}
组件挂载时,React会将ref的current属性设置成DOM元素,卸载时,再把ref的current属性设置为null。ref更新发生在componentDidMount或者componentDidUpdate生命周期回调之前。
4. 自定义组件中创建与使用
import React from 'react'
import Button from './Button'
import Input from './Input'
class Ref extends React.Component {
constructor(props) {
super(props)
// 初始化 获取挂载的组件Input实例
this.inputComponentRef = React.createRef()
}
handleClick = () => {
// 调用Input实例的方法
if(this.inputComponentRef.current) this.inputComponentRef.current.focus()
}
render() {
return (
<div>
<Button onClick={this.handleClick} />
<Input ref={this.inputComponentRef} />
</div>
)
}
}
export default Ref
同DOM中使用类似,组件挂载时,React会将ref的current属性设置成组件的实例,卸载时,再把ref的current属性设置为null。ref更新发生在componentDidMount或者componentDidUpdate生命周期回调之前。
5. 函数式组件无法为当前组件直接创建refs
const Input = () => <input />
class App extends React.Component {
constructor(props) {
super(props);
this.inputComponentRef = React.createRef();
}
render() {
// 不起作用,会报错
return (
<Input ref={this.inputComponentRef} />
);
}
}
但是,函数式组件内部可以使用ref引用属性使其指向一个DOM元素或者一个类组件
,例如:
const Input = (props) => {
let inputRef = React.createRef();
function handleClick() {
inputRef.current.focus();
}
return (
<div>
<input
type="text"
ref={inputRef} />
<button
onClick={handleClick}
>Focus</button>
</div>
)
}
6. 使用回调的方式 (推荐)
在需要声明ref的位置绑定一个方法,返回的参数是DOM节点或则实例组件,组件在加载时会自动触发该回调方法,该参数作为实例的一个属性在其他位置直接使用即可。
class Input extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
// 不需要使用current调用
this.inputRef && this.inputRef.focus();
}
initRef = (ele) => {
// 组件加载时(或者更新时)自动触发该方法
this.inputRef = ele
}
focus = () => {
if(this.inputRef) this.inputRef.focus();
}
render() {
return (
<div className="block">
<p>Input 加载时自动聚焦</p>
<input ref={this.initRef} />
</div>
)
}
}
export default Input
使用引用回调函数的注意事项
如果ref回调函数定义在内联函数(inline function)中,更新时他会被调用两次,第一次参数是null,第二次参数才是DOM元素。这是因为每个渲染都会创建一个新的函数实例,所以React需要清除旧的引用并设置新的。你可以通过将引用回调定义为该类的绑定方法来避免这种情况,但请注意,大多数情况下这样做或者不这样做都没太大关系。
7. React低版本遗留的API:字符串引用Refs
绑定一个 字符串类型的ref 属性到 render 的返回值上
<input ref="myInput" />
在其他位置(实例方法或者生命周期函数中)使用
componentDidMount() {
// 保留关键字this.refs
// 页面加载完成时使input标签自动聚焦
this.refs.myInput.focus()
}
8. 建议使用其他的解决方案替代refs
在极少的一些情况下,我们需要从父组件中访问某个子DOM节点或者子组件的一些属性和方法。一般来说不建议这么做,因为它打破了组件封装,但是它偶尔也很有用,比如触发获取焦点,或者测量一个子DOM节点的尺寸或者位置。
本文代码链接地址:https://github.com/zhiyuanMain/ReactForJianshu.git