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则解决了组件间引用传递的问题,使得组件更加灵活、复用性更强。


参考资料

相关推荐
fanruitian1 小时前
uniapp android开发 测试板本与发行版本
前端·javascript·uni-app
rayufo1 小时前
【工具】列出指定文件夹下所有的目录和文件
开发语言·前端·python
RANCE_atttackkk1 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
摘星编程2 小时前
React Native + OpenHarmony:Timeline垂直时间轴
javascript·react native·react.js
2501_944525543 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
jin1233223 小时前
React Native鸿蒙跨平台完成剧本杀组队详情页面,可以复用桌游、团建、赛事等各类组队详情页开发
javascript·react native·react.js·ecmascript·harmonyos
李白你好4 小时前
Burp Suite插件用于自动检测Web应用程序中的未授权访问漏洞
前端
刘一说5 小时前
Vue 组件不必要的重新渲染问题解析:为什么子组件总在“无故”刷新?
前端·javascript·vue.js
jin1233225 小时前
基于React Native鸿蒙跨平台移动端表单类 CRUD 应用,涵盖地址列表展示、新增/编辑/删除/设为默认等核心操作
react native·react.js·ecmascript·harmonyos
徐同保5 小时前
React useRef 完全指南:在异步回调中访问最新的 props/state引言
前端·javascript·react.js