2.6Refs React Refs 详解:访问底层DOM与管理组件实例 在 React 开发中,我们通常遵循“数据驱动视图”的原则,通过 state 和 props 来控制组件的渲染。然而,在某些情况下,我们需要直接访问底层 DOM 元素或组件实例,以便执行一些特定的操作,例如: 直接操作 DOM 元素,如聚焦输入框、播放/暂停视频等。 访问子组件的公共方法。 集成第三方库,这些库可能需要直接操作 DOM 元素。 这时,React 的 Refs 就派上了用场。Refs 提供了一种访问 DOM 节点或 React 组件实例的方式,它就像一个“引用”,允许我们绕过 React 的虚拟 DOM,直接与真实 DOM 交互。 2.6.
在 React 开发中,我们通常遵循“数据驱动视图”的原则,通过 state 和 props 来控制组件的渲染。然而,在某些情况下,我们需要直接访问底层 DOM 元素或组件实例,以便执行一些特定的操作,例如:
直接操作 DOM 元素,如聚焦输入框、播放/暂停视频等。
访问子组件的公共方法。
集成第三方库,这些库可能需要直接操作 DOM 元素。
这时,React 的 Refs 就派上了用场。Refs 提供了一种访问 DOM 节点或 React 组件实例的方式,它就像一个“引用”,允许我们绕过 React 的虚拟 DOM,直接与真实 DOM 交互。
React 提供了多种创建 Refs 的方式,最常见的是使用 React.createRef() 和 useRef() Hook。
React.createRef() (Class 组件)在 Class 组件中,我们可以使用 React.createRef() 方法创建一个 Ref 对象,并将其赋值给一个实例属性。然后,将该 Ref 对象绑定到要访问的 DOM 元素或组件上。
import React, { Component } from 'react'; class MyInput extends Component { constructor(props) { super(props); this.inputRef = React.createRef(); // 创建 Ref 对象 } componentDidMount() { // 组件挂载后,聚焦输入框 this.inputRef.current.focus(); } render() { return ( <input type="text" ref={this.inputRef} /> // 将 Ref 对象绑定到 input 元素 ); } } export default MyInput;
代码详解:
this.inputRef = React.createRef();: 在构造函数中,我们使用 React.createRef() 创建了一个 Ref 对象,并将其赋值给 this.inputRef 实例属性。这个 Ref 对象最初是 null。
ref={this.inputRef}: 在 render() 方法中,我们将 this.inputRef 绑定到 <input> 元素上。 React 会将该 input 元素的 DOM 节点赋值给 this.inputRef.current。
this.inputRef.current.focus();: 在 componentDidMount() 生命周期方法中,我们可以通过 this.inputRef.current 访问到 input 元素的 DOM 节点,并调用其 focus() 方法使其获得焦点。
图示:
useRef() Hook (函数组件)在函数组件中,我们使用 useRef() Hook 创建 Ref 对象。 useRef() 返回一个具有 .current 属性的可变对象,该对象在组件的整个生命周期内保持不变。
import React, { useRef, useEffect } from 'react'; function MyInput() { const inputRef = useRef(null); // 创建 Ref 对象,初始值为 null useEffect(() => { // 组件挂载后,聚焦输入框 inputRef.current.focus(); }, []); // 依赖项为空数组,确保只在组件挂载时执行一次 return ( <input type="text" ref={inputRef} /> // 将 Ref 对象绑定到 input 元素 ); } export default MyInput;
代码详解:
const inputRef = useRef(null);: 使用 useRef(null) 创建一个 Ref 对象,并将其赋值给 inputRef 变量。 初始值为 null,因为在组件首次渲染时,DOM 节点尚未创建。
ref={inputRef}: 将 inputRef 绑定到 <input> 元素上。 React 会将该 input 元素的 DOM 节点赋值给 inputRef.current。
useEffect(() => { ... }, []);: 使用 useEffect Hook 在组件挂载后执行副作用。 useEffect 的第二个参数是一个空数组 [],这意味着该副作用只会在组件首次挂载时执行一次。
inputRef.current.focus();: 在 useEffect 内部,我们可以通过 inputRef.current 访问到 input 元素的 DOM 节点,并调用其 focus() 方法使其获得焦点。
图示:
除了绑定到 DOM 元素,Refs 还可以绑定到 React 组件。 这允许父组件访问子组件的实例方法。
import React, { Component, forwardRef, useImperativeHandle, useRef } from 'react'; // Class 组件 class MyInput extends Component { focus() { this.inputElement.focus(); } render() { return ( <input type="text" ref={(el) => this.inputElement = el} /> ); } } // 函数组件 - 使用 forwardRef 和 useImperativeHandle const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} /> }); class ParentComponent extends Component { constructor(props) { super(props); this.inputRef = React.createRef(); this.fancyInputRef = React.createRef(); } componentDidMount() { // 聚焦 MyInput 组件的 input 元素 this.inputRef.current.focus(); // 聚焦 FancyInput 组件的 input 元素 this.fancyInputRef.current.focus(); } render() { return ( <div> <MyInput ref={this.inputRef} /> <FancyInput ref={this.fancyInputRef} /> </div> ); } } export default ParentComponent;
代码详解:
MyInput (Class 组件): 使用回调 Ref,将 input 元素的 DOM 节点赋值给 this.inputElement。 然后定义一个 focus 方法,调用 this.inputElement.focus()。 父组件可以通过 this.inputRef.current.focus() 调用该方法。
FancyInput (函数组件):
forwardRef: 使用 forwardRef 高阶组件,将 ref 传递给函数组件。 forwardRef 接收一个渲染函数,该函数接收 props 和 ref 作为参数。
useImperativeHandle: 使用 useImperativeHandle Hook 自定义暴露给父组件的实例值。 useImperativeHandle 接收 ref 和一个函数作为参数。 该函数返回一个对象,该对象包含了父组件可以通过 ref.current 访问的属性和方法。
在这个例子中,useImperativeHandle 暴露了一个 focus 方法,该方法调用 inputRef.current.focus()。 父组件可以通过 this.fancyInputRef.current.focus() 调用该方法。
图示:
除了使用 React.createRef() 和 useRef(),还可以使用回调 Refs。 回调 Refs 允许你在组件挂载或卸载时执行自定义逻辑。
import React, { Component } from 'react'; class MyComponent extends Component { constructor(props) { super(props); this.myRef = null; // 初始化为 null } setMyRef = (element) => { this.myRef = element; // 在这里可以执行一些操作,例如: if (element) { console.log('DOM element is attached:', element); } } componentWillUnmount() { // 在组件卸载时,将 myRef 设置为 null this.myRef = null; console.log('Component is unmounting, ref is cleared.'); } render() { return ( <div ref={this.setMyRef}> This is my component. </div> ); } } export default MyComponent;
代码详解:
this.myRef = null;: 在构造函数中,将 this.myRef 初始化为 null。
setMyRef = (element) => { ... }: 定义一个回调函数 setMyRef,该函数接收一个参数 element,该参数是 DOM 元素。
当组件挂载时,React 会将 DOM 元素作为参数传递给 setMyRef 函数,并将 this.myRef 设置为该 DOM 元素。
当组件卸载时,React 会将 null 作为参数传递给 setMyRef 函数,并将 this.myRef 设置为 null。
ref={this.setMyRef}: 将 setMyRef 函数作为 ref 属性的值传递给 <div> 元素。
componentWillUnmount(): 在 componentWillUnmount 生命周期方法中,将 this.myRef 设置为 null,以确保在组件卸载后,不再持有对 DOM 元素的引用。
图示:
避免过度使用 Refs: 尽量使用 React 的数据流机制 (state 和 props) 来控制组件的渲染。 只有在必要时才使用 Refs。
不要在 render 方法中访问 Refs: 在 render 方法中访问 Refs 可能会导致性能问题,因为 render 方法可能会被频繁调用。 应该在 componentDidMount、componentDidUpdate 或事件处理函数中访问 Refs。
小心使用回调 Refs: 确保在组件卸载时清除回调 Refs,以避免内存泄漏。
函数组件中使用 useRef Hook: 在函数组件中,应该使用 useRef Hook 来创建 Refs,而不是 React.createRef()。
Refs 是 React 提供的一种访问底层 DOM 元素或组件实例的方式。 通过 Refs,我们可以绕过 React 的虚拟 DOM,直接与真实 DOM 交互,执行一些特定的操作。 然而,应该谨慎使用 Refs,避免过度使用,并注意一些潜在的问题,例如性能问题和内存泄漏。 在大多数情况下,应该优先使用 React 的数据流机制 (state 和 props) 来控制组件的渲染。