React 中的 useRef 与 forwardRef:深入理解与实战应用

引言

在 React 开发中,useRef 和 forwardRef 是两个非常实用但又容易被忽视的 API。它们虽然不像 useState 或 useEffect 那样高频使用,但在处理 DOM 操作、组件间引用传递等场景中,却有着不可替代的作用。

本文将从基础概念讲起,逐步深入,结合代码示例和实战场景,带你全面掌握 useRef 和 forwardRef 的使用技巧与最佳实践。

一、什么是 useRef?

useRef 是 React 提供的一个 Hook,用于在函数组件中创建一个"可变的引用对象",其.current属性被初始化为传入的参数(通常为null)。它在组件的整个生命周期中保持不变,不会引起组件重新渲染。

常见用途:

  • 访问 DOM 元素:比如聚焦输入框、获取元素尺寸等。
  • 保存可变状态:类似于类组件中的实例属性,适用于不触发重新渲染的变量。
  • 跨渲染周期保存数据:避免在每次渲染中重新创建某些对象。

示例代码:

jsx 复制代码
import { useRef, useEffect } from 'react'

function App() {
  const inputRef = useRef(null)

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  return (
    <div>
      <input ref={inputRef} />
    </div>
  )
}

在这个例子中,我们使用 useRef 获取了输入框的引用,并在组件挂载后自动聚焦。

二、为什么需要 forwardRef?

React中默认情况下,ref 是不能传递给子组件的 。也就是说,如果你在父组件中通过 ref 引用了一个子组件,这个ref并不会自动绑定到子组件内部的 DOM 元素上。

这在某些场景下会带来不便,例如你想在父组件中访问子组件内部的某个输入框或按钮,这时候就需要使用 forwardRef。

三、什么是 forwardRef?

forwardRef 是一个 React 高阶组件,用于将 ref 显式地传递给子组件,从而让子组件可以接收并使用这个 ref。

它接受一个组件作为参数,并返回一个新的组件,该组件可以接收 ref 属性并将其传递给内部的 DOM 元素或其他子组件。

示例代码:

jsx 复制代码
import { forwardRef } from 'react'

const Light = forwardRef((props, ref) => {
  return (
    <div>
      <input type="text" ref={ref} />
    </div>
  )
})

function App() {
  const inputRef = useRef(null)

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  return (
    <div>
      <Light ref={inputRef} />
    </div>
  )
}

在这个例子中,我们使用 forwardRef将ref 从父组件App传递给了子组件Light,并最终绑定到了 <input>元素上,实现了聚焦功能。

四、useRef + forwardRef 实战:封装可复用组件

在实际开发中,我们经常需要封装一些可复用的 UI 组件,并希望父组件可以访问组件内部的某些 DOM 元素。此时,forwardRef 就派上了用场。

场景描述:

我们封装一个 CustomInput 组件,希望父组件可以通过 ref 调用其内部 <input> 的 focus() 方法。

步骤一:定义组件

jsx 复制代码
import { forwardRef } from 'react'

const CustomInput = forwardRef((props, ref) => {
  return (
    <div className="custom-input">
      <input type="text" ref={ref} />
    </div>
  )
})

步骤二:在父组件中使用

jsx 复制代码
function App() {
  const inputRef = useRef(null)

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  return (
    <div>
      <CustomInput ref={inputRef} />
    </div>
  )
}

这样,我们就成功地将 ref 从父组件传递到了子组件,并最终作用在了 <input> 上。

五、forwardRef 与高阶组件(HOC)结合使用

在使用高阶组件封装组件时,如果不使用 forwardRef,ref 会被包裹在 HOC 内部,导致父组件无法直接访问原始组件的 DOM。

示例:使用 forwardRef 包裹 HOC

定义输入框为组件

jsx 复制代码
function Light (props, ref) {
  console.log(props, ref);
  
  return (
    <div>
      <input type="text" />
    </div>
  )
}

  return (
    <div className='App'>
      {/* <input ref={ref} /> */}
      <Light ref={ref} />
      {/* <WrapperLight ref={ref} /> */}
    </div>
  )

这时候代码当中ref是不具有向下传递的能力的,那么如何使其具有向下传递的能力呢。

jsx 复制代码
import { 
  useRef,
  forwardRef
} from 'react'
// 返回一个全新的组件, ref 会被传递给新组件
const WrapperLight = forwardRef(Light)

  return (
    <div className='App'>
      {/* <input ref={ref} /> */}
      {/* <Light ref={ref} /> */}
      <WrapperLight ref={ref} />
    </div>
  )
}

export default App

现在咱们就在这里成功的从父组件当中拿到了子组件传递过来的ref值

完整源码

jsx 复制代码
import { 
  useRef,
  useEffect,
  forwardRef
} from 'react'

import './App.css'

function Light (props, ref) {
  console.log(props, ref);
  
  return (
    <div>
      <input type="text" ref={ref} />
    </div>
  )
}
// 返回一个全新的组件, ref 会被传递给新组件
const WrapperLight = forwardRef(Light)

function App() {
  // 父组件 ref 
  const ref = useRef(null)
  console.log(ref.current);

  useEffect(() =>{
    ref.current?.focus()
  },[])
  

  return (
    <div className='App'>
      {/* <input ref={ref} /> */}
      {/* <Light ref={ref} /> */}
      <WrapperLight ref={ref} />
    </div>
  )
}

export default App

六、最佳实践与注意事项

1. 不要滥用 ref

  • ref 应用于非受控组件或需要直接访问 DOM 的场景。
  • 尽量使用 React 的声明式方式(如 useState)来管理状态,而不是频繁操作 DOM。

2. 使用可选链运算符提升代码安全性

当访问 ref.current 时,使用可选链操作符 ?. 可以避免空值导致的错误:

jsx 复制代码
useEffect(() => {
  inputRef.current?.focus()
}, [])

3. forwardRef 仅适用于函数组件

  • 如果你使用的是类组件,可以使用 React.createRef() 或 ref 回调函数。
  • forwardRef 是为函数组件设计的,是 React 推荐的方式。

七、总结

API 作用 使用场景
useRef 创建一个可变引用对象,跨渲染周期保持数据 操作 DOM、保存状态
forwardRef 将 ref 传递给子组件 组件封装、高阶组件中访问内部 DOM

在React开发中,useRef和forwardRef是一对非常实用的搭档。useRef帮助我们管理组件内部的状态和DOM元素,而forwardRef则解决了组件间引用传递的问题,使得组件更加灵活、复用性更强。


参考资料

相关推荐
mldong10 小时前
一个 npm 包,九页审批中心:Vue3 项目接上工作流引擎
前端·javascript·vue.js
架构技术专栏18 小时前
Agent 工具调用:从工具声明到执行反馈
前端·人工智能·后端
别动我齐刘海19 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
志尊宝20 小时前
Vue3 零基础每日笔记(071):暗黑模式与主题切换——CSS 变量 + Element Plus 暗黑方案
前端·css·笔记·vue·html·前端开发·软件开发
刘发财20 小时前
前端如何生成10000页PDF文件?
前端·javascript·开源
A接拉起00721 小时前
2026 年 JavaScript 开发者需要了解的知识
前端·javascript·后端
用户114818678948421 小时前
面试官:大屏项目里十几个模块同时渲染上万条数据,怎么做到不卡?
前端·性能优化
前端市界21 小时前
Pro React Admin: 统一工程底座,承载多种应用...
前端·github
葡萄城技术团队21 小时前
dsh-spreadjs-editor 当前支持什么:安装前需要了解的几件事
前端
三*一1 天前
优化修整算法
前端·javascript·算法