当前位置: 首页 > news >正文

React:useRef 超详细教程、forwardRef 详解、useImperativeHandle详解

文章目录

  • 一、React useRef 超详细教程
    • 1. 什么是 useRef?
    • 2. 场景一:访问 DOM 节点(最常见)
      • 代码示例:自动聚焦输入框
    • 3. 场景二:存储“不需要 UI 感知”的变量
    • 4. useRef vs useState:深度对比
    • 5. 核心注意事项(避坑指南)
      • ① 不要在渲染期间读写 .current
      • ② 只有在必要时才使用 Ref
      • ③ Ref 无法在函数组件上直接使用
    • 6. 总结
  • 二、 forwardRef 详解:打破组件黑盒
    • 1. 为什么需要 forwardRef?
    • 2. 如何使用 forwardRef
    • 3. 进阶用法:结合 useImperativeHandle

一、React useRef 超详细教程

在 React 的世界里,useState负责驱动 UI 更新,而useRef则是那个“静默的观察者”。它非常强大,但如果用错了,会让你的代码变得难以维护。

这篇教程将带你深度拆解useRef的核心逻辑、应用场景以及它与useState的本质区别。


1. 什么是 useRef?

useRef返回一个可变的ref 对象,其.current属性被初始化为传入的参数。它有两个核心特性:

  • 跨渲染持久化:在组件的整个生命周期内,这个对象保持不变。
  • 更新不触发重新渲染:修改.current的值不会导致组件重新渲染(这是它与useState最大的区别)。

2. 场景一:访问 DOM 节点(最常见)

在 React 中,我们通常通过propsstate来管理 UI,但有时你需要直接操作底层的 DOM 元素(例如:聚焦输入框、滚动到特定位置、调用浏览器 API)。

代码示例:自动聚焦输入框

import{useRef}from'react';functionTextInputWithFocusButton(){// 1. 初始化 ref,初始值为 nullconstinputEl=useRef(null);constonButtonClick=()=>{// 3. 通过 .current 访问真实的 DOM 节点// 当组件挂载后,inputEl.current 将指向真实的 <input> 元素if(inputEl.current){inputEl.current.focus();}};return(<><input ref={inputEl}type="text"/><button onClick={onButtonClick}>聚焦输入框</button></>);}

3. 场景二:存储“不需要 UI 感知”的变量

有时候你需要记录一些数据,这些数据在改变时不应该触发页面刷新。比如计时器 ID、前一次的 Props 值,或者记录某种操作的次数。

代码示例:秒表计时器

import{useState,useRef}from'react';functionStopwatch(){const[startTime,setStartTime]=useState(null);const[now,setNow]=useState(null);// 使用 useRef 存储 interval ID,因为改变它不需要更新 UIconstintervalRef=useRef(null);functionhandleStart(){setStartTime(Date.now());setNow(Date.now());clearInterval(intervalRef.current);// 将计时器 ID 存入 refintervalRef.current=setInterval(()=>{setNow(Date.now());},10);}functionhandleStop(){// 停止计时,直接从 ref 中取 ID,不会引起额外的渲染clearInterval(intervalRef.current);}letsecondsPassed=0;if(startTime!=null&&now!=null){secondsPassed=(now-startTime)/1000;}return(<><h1>时间:{secondsPassed.toFixed(3)}</h1><button onClick={handleStart}>开始</button><button onClick={handleStop}>停止</button></>);}

4. useRef vs useState:深度对比

特性useStateuseRef普通变量 (let/const)
返回值[state, setState]{ current: ... }变量本身
修改方式调用setState(newValue)直接修改ref.current = newValue直接重新赋值
触发渲染触发组件 Re-render不会触发渲染不会触发渲染
持久性渲染间持久化(重绘后值保留)渲染间持久化(重绘后值保留)无法持久化(每次函数执行都会重置)
用途存储驱动 UI 显示的数据(状态)存储 DOM 节点、Timer ID 或不需要展示在页面上的逻辑变量临时计算、函数内部的局部逻辑
同步/异步状态更新通常是异步的(在闭包中读取旧值)修改是同步的,值立即改变同步修改

5. 核心注意事项(避坑指南)

① 不要在渲染期间读写 .current

React 期望组件是纯函数。如果你在 return 之前直接修改 ref.current,可能会导致难以预测的 Bug。

❌ 错误写法:

functionMyComponent(){constmyRef=useRef(0);myRef.current=myRef.current+1;// 严禁在渲染过程中修改return<div>{myRef.current}</div>;}
  • ✅ 正确写法:
    useEffect或事件处理函数(Event Handlers)中操作。

② 只有在必要时才使用 Ref

如果你可以通过stateprops实现功能,优先使用它们。Ref 相当于 React 的“紧急出口”,过度使用会让你的应用逻辑变得难以追踪。

import{useRef,useEffect,useState}from'react';functionMyComponent(){constmyRef=useRef(0);const[count,setCount]=useState(0);useEffect(()=>{// ✅ 正确:在渲染完成后执行副作用myRef.current=myRef.current+1;console.log("当前 Ref 的值是:",myRef.current);});return(<div><p>Ref 值(仅在控制台查看最新):{myRef.current}</p><button onClick={()=>setCount(c=>c+1)}>重新渲染组件</button></div>);}

③ Ref 无法在函数组件上直接使用

如果你想给一个函数组件添加ref属性,会报错。

  • 原因:函数组件没有实例。
  • 解决方案:使用forwardRefAPI 将 ref 转发到子组件内部的 DOM。

6. 总结

  • useRef就像一个“盒子”,你在里面放任何东西,React 都会帮你存着,直到组件销毁。
  • 它是操作DOM的官方指定通道。
  • 它是存储“静默变量”(不影响 UI 的变量)的绝佳地点。
  • 关键结论:改 ref 不会刷页面!

二、 forwardRef 详解:打破组件黑盒

在 React 中,组件就像一个黑盒。默认情况下,你不能从父组件直接获取子组件内部的 DOM 节点或组件实例。这种限制是为了保证组件的封装性。

forwardRef(引用转发)就是为了打破这种限制,允许组件像传递普通 Props 一样,将 ref 转发给其子节点。

1. 为什么需要 forwardRef?

假设你封装了一个基础按钮组件 MyButton:

functionMyButton(props){return<button className="btn">{props.children}</button>;}

如果你想在父组件中让这个按钮自动聚焦:

constbtnRef=useRef(null);// ...<MyButton ref={btnRef}>点击</MyButton>

结果: btnRef.current 会是 null。
原因: React 默认不会把 ref 作为一个 prop 传给组件。ref 属性被 React 特殊处理了,就像 key 一样,不会出现在 props 对象中。


2. 如何使用 forwardRef

forwardRef 接受一个渲染函数,该函数接收两个参数:props 和 ref。

import{forwardRef}from'react';constMyButton=forwardRef((props,ref)=>{return(<button ref={ref}className="btn">{props.children}</button>);});
functionParent(){constbtnRef=useRef(null);consthandleClick=()=>{// 成功获取子组件内部的 button 节点btnRef.current.focus();};return(<MyButton ref={btnRef}onClick={handleClick}>Focus Me</MyButton>);}

3. 进阶用法:结合 useImperativeHandle

有时候,你不想把整个 DOM 节点暴露给父组件,而只想暴露特定的方法(例如:只允许父组件调用 focus,但不允许修改样式)。

这时需要配合 useImperativeHandle Hook:

import{forwardRef,useRef,useImperativeHandle}from'react';constFancyInput=forwardRef((props,ref)=>{constinputRef=useRef();// 自定义暴露给父组件的实例值useImperativeHandle(ref,()=>({focus:()=>{inputRef.current.focus();},shake:()=>{console.log("正在抖动输入框...");}}));return<input ref={inputRef}/>;});

父组件: 现在 ref.current 只有 { focus, shake } 这两个方法,而拿不到真实的 DOM 节点。这符合最小暴露原则。

http://www.jsqmd.com/news/780532/

相关文章:

  • 芯片设计首次流片成功的关键技术与实践
  • 多核架构与嵌入式系统:性能优化与协处理器设计
  • 深入解析PHP表单处理:Ajax与Checkbox数组的完美结合
  • Arm Neoverse V3AE核心调试与性能监控技术解析
  • 解决Nx Cloud超限问题:实战案例解析
  • 具身智能实践:从AI智能体到机械爪的软硬件协同开发指南
  • LoRA微调工程完全手册2026:从数据准备到生产部署
  • TMS320C6000平台H.263解码器优化实现
  • ClawLayer框架解析:构建高可用的异步网络爬虫系统
  • Bitwarden CLI自动化集成:安全密码管理与CI/CD实践
  • 硬件创新与TTM平衡:从芯片设计到产品落地的系统工程实践
  • Silicon Labs BG27/MG27无线SoC在医疗物联网中的应用解析
  • 自动化流程守护框架:基于状态机与看门狗机制构建稳定RPA系统
  • 2026年民宿用免打孔妇洗器定制加工厂家推荐 - 品牌宣传支持者
  • 基于Markdown的多智能体协作框架:提升LLM编程效率的工程化实践
  • [Deep Agents:LangChain的Agent Harness-03]FilesystemMiddleware:赋能Agent读写文件及管理长上下文
  • FastAPI扩展库实战:构建生产级API服务的标准化工具箱
  • Codebase Digest:Python命令行工具,为LLM分析代码库生成结构化摘要
  • 抖音直播间数据抓取终极指南:5分钟实现实时弹幕监控
  • 开源机械爪OpenClaw:从3D打印到力控的完整机器人抓取方案
  • PM2-VSCode扩展:在编辑器内无缝管理Node.js进程,提升开发效率
  • AI代理操作系统oh-my-openagent:智能编排多模型,提升开发效率
  • 程序员如何通过“技术写作”实现被动收入?
  • 【懒人运维】rsyslog+mysql+loganalyzer 日志服务器搭建
  • 使用CGAL构建完美球体网格
  • 2026年分布式坐席制造商口碑榜:这几家最靠谱
  • 微信小程序跑腿平台(30263)
  • 【STM32】启动过程分析
  • Windows光标转Linux主题:Project Sekai风格光标自动化转换指南
  • 原神144帧终极解锁指南:告别60帧限制,体验丝滑战斗