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

引言

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

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

一、什么是 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>
  )
}

在这个例子中,我们使用 forwardRefref 从父组件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)结合使用

在使用高阶组件封装组件时,如果不使用 forwardRefref 会被包裹在 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开发中,useRefforwardRef是一对非常实用的搭档。useRef帮助我们管理组件内部的状态和DOM元素,而forwardRef则解决了组件间引用传递的问题,使得组件更加灵活、复用性更强。


参考资料

相关推荐
在雨季等你14 分钟前
奋斗在创业路上的老开发
android·前端·后端
yume_sibai21 分钟前
Vue 生命周期
前端·javascript·vue.js
阿廖沙102438 分钟前
前端不改后端、不开 Node,彻底搞定 Canvas 跨域下载 —— wsrv.nl 野路子实战指南
前端
讨厌吃蛋黄酥39 分钟前
🌟 React Router Dom 终极指南:二级路由与 Outlet 的魔法之旅
前端·javascript
花颜yyds39 分钟前
three.js学习
前端·three.js
SixHateSeven40 分钟前
🚀 TSX动态编译的黑科技,快如闪电!
前端·编译器
aiwery41 分钟前
前端国际化技术实践
前端
兵临天下api1 小时前
电商数据分析实战:利用 API 构建商品价格监控系统
前端
迷曳1 小时前
32、鸿蒙Harmony Next开发:使用动画-动画概述
前端·华为·动画·harmonyos
FogLetter1 小时前
React中的forwardRef:打破父子组件间的"隔墙"
前端·react.js