在React组件开发中,ref是获取DOM元素、类组件实例的重要方式,官方提供了createRef方法来创建ref对象,同时开发者也可以根据自身需求封装自定义Ref对象。两种方式都能实现引用管理,但适用场景和底层逻辑存在明显差异,需要根据实际需求做出选择。

createRef的基本用法与原理
createRef是React官方提供的用于创建ref对象的方法,调用后会返回一个可变的ref对象,其current属性会被初始化为null,当ref被挂载到组件或DOM元素上时,current属性会被赋值为对应的实例或DOM节点。
以下是createRef的基础使用示例:
import React, { createRef, Component } from 'react';
class DemoComponent extends Component {
constructor(props) {
super(props);
// 创建ref对象
this.inputRef = createRef();
}
handleFocus = () => {
// 通过current属性获取DOM节点并调用方法
this.inputRef.current.focus();
};
render() {
return (
<div>
<input ref={this.inputRef} type="text" />
<button onClick={this.handleFocus}>聚焦输入框</button>
</div>
);
}
}
export default DemoComponent;
createRef的底层实现非常简单,核心逻辑就是返回一个包含current属性的对象:
function createRef() {
const refObject = {
current: null
};
return refObject;
}
需要注意的是,createRef每次调用都会返回一个新的ref对象,因此在类组件中通常会将createRef的调用放在构造函数中,避免每次渲染都创建新的ref导致不必要的更新。
自定义Ref对象的实现方式
自定义Ref对象本质上是开发者自行封装的、符合ref使用规范的对象,核心要求也是包含current属性,因为React在挂载ref时,只会检查对象是否存在current属性并进行赋值。常见的自定义Ref场景包括需要监听ref变化、扩展ref能力等情况。
以下是一个自定义Ref对象的示例,实现了ref赋值时的监听功能:
import React, { Component } from 'react';
class CustomRefDemo extends Component {
constructor(props) {
super(props);
// 自定义ref对象,添加赋值监听逻辑
this.customRef = {
current: null,
// 重写current的赋值逻辑
set _current(value) {
console.log('ref被赋值,值为:', value);
this.current = value;
}
};
}
componentDidMount() {
// 手动触发ref赋值(实际场景中由React自动赋值)
this.customRef._current = document.getElementById('customInput');
}
render() {
return (
<div>
<input id="customInput" type="text" />
</div>
);
}
}
export default CustomRefDemo;
更常见的自定义Ref场景是结合回调函数使用,回调函数形式的ref本身也可以看作一种自定义Ref的实现,React会在组件挂载或卸载时调用回调函数,传入对应的DOM节点或实例:
import React, { Component } from 'react';
class CallbackRefDemo extends Component {
handleInputRef = (node) => {
// node为对应的DOM节点,挂载时为节点,卸载时为null
if (node) {
console.log('输入框已挂载');
node.focus();
} else {
console.log('输入框已卸载');
}
this.inputNode = node;
};
render() {
return (
<div>
<input ref={this.handleInputRef} type="text" />
</div>
);
}
}
export default CallbackRefDemo;
两者的核心差异对比
createRef和自定义Ref对象的核心差异主要体现在以下几个方面:
| 对比维度 | createRef | 自定义Ref对象 |
|---|---|---|
| 创建方式 | 调用React官方提供的createRef方法 | 开发者自行封装对象,只要包含current属性即可 |
| 可扩展性 | 仅包含current属性,无法直接扩展额外逻辑 | 可以自定义current的读写逻辑,添加额外方法或监听 |
| 适用场景 | 常规DOM获取、类组件实例获取场景 | 需要监听ref变化、扩展ref能力、特殊引用管理场景 |
| 函数组件兼容性 | 函数组件中可以使用,但每次渲染会创建新对象,通常配合useRef使用 | 函数组件中也可以自定义,但需要自行管理对象稳定性 |
场景选择建议
优先选择createRef的场景
- 仅需要获取DOM元素或类组件实例,不需要额外的监听或扩展逻辑
- 在类组件中使用ref,不需要在函数组件重新渲染时保持ref对象的稳定性
- 项目遵循React官方推荐的最佳实践,不需要自定义ref相关逻辑
优先选择自定义Ref对象的场景
- 需要在ref被赋值或清空时执行额外的逻辑,比如打日志、触发回调
- 需要给ref对象添加额外的属性或方法,扩展ref的能力
- 使用回调函数形式的ref,需要更灵活的引用管理逻辑
- 需要兼容旧版本的React,或者有特殊的上层框架集成需求
注意事项
无论是使用createRef还是自定义Ref对象,都需要注意以下几点:
- ref不能用于函数组件,因为函数组件没有实例,若需要获取函数组件的引用,需要将其转为类组件或者使用forwardRef
- 不要在渲染过程中读取或写入ref的current属性,可能会导致不可预期的问题
- 自定义Ref对象时,必须保证存在current属性,否则React无法正确完成ref的挂载赋值
- 在函数组件中如果需要使用类似createRef的能力,官方推荐使用useRef,useRef返回的对象会在组件的整个生命周期内保持不变,避免每次渲染创建新对象的问题
以下是函数组件中使用useRef的示例,它本质和createRef类似,但更适合函数组件场景:
import React, { useRef, useEffect } from 'react';
function FunctionRefDemo() {
// useRef创建ref,对象在组件生命周期内保持稳定
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<div>
<input ref={inputRef} type="text" />
</div>
);
}
export default FunctionRefDemo;