在React组件开发中,ref是用于获取组件实例或者DOM元素的重要API,官方提供了createRef()方法来创建ref对象,但不少开发者会尝试用普通对象{ current: null }来替代,这种做法看似可行,实际会埋下很多隐患。本文将详细分析两者的差异,解释为什么不能直接使用普通对象。

普通对象创建ref的问题
首先我们来看直接使用普通对象作为ref的场景,假设我们有一个类组件,尝试用普通对象绑定input元素:
import React, { Component } from 'react';
class NormalRefDemo extends Component {
constructor(props) {
super(props);
// 使用普通对象作为ref
this.inputRef = { current: null };
}
componentDidMount() {
console.log('普通对象ref的current值:', this.inputRef.current);
// 尝试让input自动聚焦
if (this.inputRef.current) {
this.inputRef.current.focus();
}
}
render() {
return (
<div>
<input type="text" ref={this.inputRef} />
<button onClick={() => this.inputRef.current.focus()}>聚焦输入框</button>
</div>
);
}
}
export default NormalRefDemo;
运行上述代码后,你会发现组件挂载完成后,this.inputRef.current依然是null,点击按钮也无法触发聚焦,这是因为普通对象没有和React的更新流程绑定。
createRef()的工作原理
React的createRef()方法创建的是一个特殊的ref对象,这个对象会被React内部识别,在组件渲染、更新、卸载的不同阶段,React会自动对ref的current属性进行赋值和清理。我们来看使用createRef()的正确示例:
import React, { Component, createRef } from 'react';
class CreateRefDemo extends Component {
constructor(props) {
super(props);
// 使用createRef创建ref
this.inputRef = createRef();
}
componentDidMount() {
console.log('createRef创建的ref的current值:', this.inputRef.current);
// 自动聚焦成功
if (this.inputRef.current) {
this.inputRef.current.focus();
}
}
render() {
return (
<div>
<input type="text" ref={this.inputRef} />
<button onClick={() => this.inputRef.current.focus()}>聚焦输入框</button>
</div>
);
}
}
export default CreateRefDemo;
这段代码运行后,组件挂载完成时this.inputRef.current会正确指向input DOM元素,点击按钮也能正常触发聚焦。
核心差异对比
我们可以通过下表清晰看到普通对象和createRef()创建ref的核心区别:
| 对比维度 | 普通对象ref | createRef()创建ref |
|---|---|---|
| 是否被React内部识别 | 否 | 是 |
| 渲染时是否自动赋值current | 否 | 是 |
| 组件卸载时是否自动清理current | 否 | 是 |
| 是否支持跨渲染周期保持引用稳定 | 不稳定,可能被重新创建 | 稳定,引用不会随渲染变化 |
为什么普通对象无法被赋值
React在处理ref的时候,会判断传入的ref是否是合法的ref对象,只有createRef()或者useRef()创建的ref才会被React的协调器处理,在DOM元素渲染完成后,将对应的DOM实例或者组件实例赋值给ref的current属性。普通对象没有这个标识,React会直接忽略对它的赋值操作,所以current始终为初始值null。
另外,当组件卸载时,createRef()创建的ref会自动将current设置为null,避免内存泄漏,而普通对象不会触发这个清理逻辑,如果后续代码还持有这个普通对象的引用,可能会导致访问到已经卸载的组件实例,引发异常。
函数组件中的ref使用
在函数组件中,我们通常使用useRef()来创建ref,它的原理和createRef()类似,同样不能直接用普通对象替代:
import React, { useRef, useEffect } from 'react';
function FunctionRefDemo() {
// 使用useRef创建ref,不能用{ current: null }替代
const inputRef = useRef(null);
useEffect(() => {
console.log('函数组件中ref的current值:', inputRef.current);
inputRef.current.focus();
}, []);
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>聚焦输入框</button>
</div>
);
}
export default FunctionRefDemo;
如果在函数组件中用普通对象作为ref,同样会出现current无法赋值的问题,因为每次组件渲染时,普通对象都会被重新创建,之前的引用也会丢失,无法正确绑定到DOM元素上。
总结
createRef()和useRef()是React专门为ref场景设计的API,它们创建的ref对象会和React的整个生命周期绑定,保证ref的赋值、清理、引用稳定都符合React的运行逻辑。普通对象没有这些特性,直接使用会导致ref无法正确绑定、组件更新异常、内存泄漏等问题,因此在React开发中,应当始终使用官方提供的createRef()或者useRef()来创建ref,避免不必要的bug。