在React的单向数据流体系下,父组件通常通过props向子组件传递数据,但在某些场景下,我们需要直接访问DOM元素或者类组件的实例,这时就需要用到Refs特性。Refs能够让我们绕过props的传递,直接获取到组件或DOM的引用,实现更灵活的交互逻辑。
Refs的核心概念
Refs是React提供的一种特殊属性,用来引用渲染结果中的DOM节点或者类组件实例。它并不是响应式的,修改Refs的值不会触发组件的重新渲染,这一点和state有本质区别。Refs的常见使用场景包括管理焦点、文本选择、媒体播放控制,以及触发强制动画等。
创建Refs的方式
在类组件中,我们通常使用React.createRef()来创建Refs对象,该对象包含一个current属性,用来存储被引用的节点或实例。在函数组件中可以使用useRef钩子,不过本文主要围绕类组件场景展开说明。
// 类组件中创建Refs的示例
import React from 'react';
class DemoComponent extends React.Component {
constructor(props) {
super(props);
// 创建Refs对象
this.myRef = React.createRef();
}
render() {
return <div>示例组件</div>;
}
}
Refs与原生DOM组件的关联
当Refs绑定到原生DOM组件(比如<input>、<button>等)时,ref.current会指向该DOM元素本身,我们可以直接调用DOM元素的方法或者访问其属性。
绑定DOM组件的示例
下面通过一个输入框自动聚焦的例子展示Refs和DOM组件的关联:
import React from 'react';
class InputFocusDemo extends React.Component {
constructor(props) {
super(props);
// 创建用于绑定input的Refs
this.inputRef = React.createRef();
}
componentDidMount() {
// 组件挂载完成后,让输入框自动获取焦点
this.inputRef.current.focus();
}
render() {
return (
<div>
<input type="text" ref={this.inputRef} placeholder="请输入内容" />
<button onClick={() => this.inputRef.current.focus()}>点击聚焦输入框</button>
</div>
);
}
}
export default InputFocusDemo;
在这个例子中,this.inputRef被绑定到原生的<input>元素上,this.inputRef.current就是该input的DOM节点,因此可以直接调用focus()方法实现聚焦功能。
Refs与类组件的关联
当Refs绑定到类组件时,ref.current会指向该类的实例,因此我们可以访问该实例的所有props、state以及自定义方法。
绑定类组件的示例
下面创建一个子组件,父组件通过Refs获取子组件的实例并调用其方法:
import React from 'react';
// 子组件,是一个类组件
class ChildComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
// 子组件的自定义方法
incrementCount() {
this.setState(prevState => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<p>子组件计数:{this.state.count}</p>
</div>
);
}
}
// 父组件
class ParentComponent extends React.Component {
constructor(props) {
super(props);
// 创建用于绑定子组件的Refs
this.childRef = React.createRef();
}
handleClick = () => {
// 通过Refs调用子组件的方法
this.childRef.current.incrementCount();
};
render() {
return (
<div>
<ChildComponent ref={this.childRef} />
<button onClick={this.handleClick}>调用子组件方法增加计数</button>
</div>
);
}
}
export default ParentComponent;
这里父组件的this.childRef绑定到了ChildComponent类组件上,this.childRef.current就是ChildComponent的实例,因此可以直接调用该实例的incrementCount方法,也可以访问其state中的数据。
使用Refs的注意事项
- 不要在函数组件的render方法中创建Refs,因为每次渲染都会重新创建,导致Refs无法稳定关联目标。
- Refs不能绑定到函数组件上,因为函数组件没有实例,如果需要给函数组件绑定Refs,需要使用
forwardRef进行包装。 - 不要过度使用Refs,优先使用props和state实现数据传递和交互,只有必须直接操作DOM或者获取组件实例时才使用Refs。
- 修改
ref.current的值不会触发组件重新渲染,因此如果需要触发渲染,还是要通过修改state实现。
Refs回调函数的使用方式
除了使用React.createRef()创建Refs对象,我们还可以使用回调函数的方式设置Refs。回调函数会在组件挂载时执行,参数是被引用的节点或实例,卸载时参数会是null。
import React from 'react';
class CallbackRefDemo extends React.Component {
constructor(props) {
super(props);
this.inputElement = null;
}
setInputRef = (element) => {
this.inputElement = element;
};
componentDidMount() {
if (this.inputElement) {
this.inputElement.focus();
}
}
render() {
return (
<div>
<input type="text" ref={this.setInputRef} placeholder="回调函数Refs示例" />
</div>
);
}
}
export default CallbackRefDemo;
这种方式的灵活性和React.createRef()基本一致,开发者可以根据实际场景选择使用哪种方式创建Refs。