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

01-React基础入门——11-Refs 与 DOM 操作

Refs 与 DOM 操作

一、Refs 概述

1.1 什么是 Refs?

Refs 是 React 提供的"逃生舱",用于直接访问 DOM 元素或组件实例。在大多数情况下应该使用声明式的 React 数据流,但有些场景必须直接操作 DOM。

1.2 何时使用 Refs

场景是否推荐使用 Refs
管理焦点、文本选择✅ 推荐
媒体播放控制(video/audio)✅ 推荐
触发强制动画✅ 推荐
集成第三方 DOM 库✅ 推荐
获取元素尺寸位置✅ 推荐
通过 props 传递数据❌ 应使用 state
触发状态更新❌ 应使用 state

二、创建和使用 Refs

2.1 useRef Hook(函数组件)

function TextInputWithFocus() { const inputRef = useRef(null); const focusInput = () => { inputRef.current.focus(); }; return ( <div> <input ref={inputRef} type="text" placeholder="输入内容" /> <button onClick={focusInput}>聚焦输入框</button> </div> ); }

2.2 createRef(类组件)

class TextInput extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); } focusInput = () => { this.inputRef.current.focus(); }; render() { return ( <div> <input ref={this.inputRef} type="text" /> <button onClick={this.focusInput}>聚焦</button> </div> ); } }

2.3 回调 Refs

function CallbackRef() { const [height, setHeight] = useState(0); const measureRef = useCallback((node) => { if (node !== null) { setHeight(node.getBoundingClientRect().height); } }, []); return ( <div ref={measureRef}> 我的高度是: {height}px </div> ); }

三、Refs 常见操作

3.1 管理焦点

function AutoFocusForm() { const nameRef = useRef(null); const emailRef = useRef(null); const passwordRef = useRef(null); const handleKeyDown = (e, nextRef) => { if (e.key === 'Enter') { nextRef.current.focus(); } }; useEffect(() => { nameRef.current.focus(); // 自动聚焦第一个输入框 }, []); return ( <form> <input ref={nameRef} onKeyDown={(e) => handleKeyDown(e, emailRef)} placeholder="姓名" /> <input ref={emailRef} onKeyDown={(e) => handleKeyDown(e, passwordRef)} placeholder="邮箱" /> <input ref={passwordRef} type="password" placeholder="密码" /> </form> ); }

3.2 媒体控制

function VideoPlayer({ src }) { const videoRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const togglePlay = () => { if (isPlaying) { videoRef.current.pause(); } else { videoRef.current.play(); } setIsPlaying(!isPlaying); }; return ( <div> <video ref={videoRef} src={src} width="100%" controls /> <button onClick={togglePlay}>{isPlaying ? '暂停' : '播放'}</button> </div> ); }

3.3 获取元素尺寸

function MeasureElement() { const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); const elementRef = useRef(null); useEffect(() => { const updateDimensions = () => { if (elementRef.current) { const { width, height } = elementRef.current.getBoundingClientRect(); setDimensions({ width, height }); } }; updateDimensions(); window.addEventListener('resize', updateDimensions); return () => window.removeEventListener('resize', updateDimensions); }, []); return ( <div> <div ref={elementRef} style={{ width: '50%', padding: '20px', background: '#f0f0f0' }} > 尺寸: {dimensions.width}px x {dimensions.height}px </div> </div> ); }

3.4 滚动控制

function ScrollController() { const listRef = useRef(null); const scrollToBottom = () => { listRef.current.scrollTop = listRef.current.scrollHeight; }; const scrollToTop = () => { listRef.current.scrollTop = 0; }; return ( <div> <div ref={listRef} style={{ height: '200px', overflow: 'auto', border: '1px solid #ccc' }} > {Array.from({ length: 50 }, (_, i) => ( <div key={i}>第 {i + 1} 项</div> ))} </div> <button onClick={scrollToTop}>滚动到顶部</button> <button onClick={scrollToBottom}>滚动到底部</button> </div> ); }

3.5 存储可变值

function TimerWithRef() { const [count, setCount] = useState(0); const intervalRef = useRef(null); const startTimer = () => { if (intervalRef.current) return; intervalRef.current = setInterval(() => { setCount(c => c + 1); }, 1000); }; const stopTimer = () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; useEffect(() => { return () => stopTimer(); // 清理 }, []); return ( <div> <p>计数: {count}</p> <button onClick={startTimer}>开始</button> <button onClick={stopTimer}>停止</button> <button onClick={() => setCount(0)}>重置</button> </div> ); }

四、转发 Refs (forwardRef)

4.1 基础转发

// 子组件 const FancyInput = forwardRef((props, ref) => { return <input ref={ref} className="fancy-input" {...props} />; }); // 父组件 function Parent() { const inputRef = useRef(null); const focusInput = () => { inputRef.current.focus(); }; return ( <div> <FancyInput ref={inputRef} placeholder="自定义输入框" /> <button onClick={focusInput}>聚焦</button> </div> ); }

4.2 转发多个 Refs

const FormInput = forwardRef((props, ref) => { const inputRef = useRef(null); // 暴露多个方法 useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), blur: () => inputRef.current.blur(), getValue: () => inputRef.current.value, setValue: (value) => { inputRef.current.value = value; } })); return <input ref={inputRef} {...props} />; }); function Parent() { const formRef = useRef(null); return ( <div> <FormInput ref={formRef} placeholder="输入内容" /> <button onClick={() => formRef.current.focus()}>聚焦</button> <button onClick={() => console.log(formRef.current.getValue())}> 获取值 </button> </div> ); }

4.3 useImperativeHandle

const CustomInput = forwardRef((props, ref) => { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, shake: () => { inputRef.current.style.transform = 'translateX(5px)'; setTimeout(() => { inputRef.current.style.transform = 'translateX(0)'; }, 100); }, clear: () => { inputRef.current.value = ''; } })); return <input ref={inputRef} {...props} />; });

五、第三方库集成

5.1 集成 Swiper

import Swiper from 'swiper'; function SwiperComponent() { const swiperRef = useRef(null); useEffect(() => { swiperRef.current = new Swiper('.swiper-container', { slidesPerView: 1, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' } }); return () => { swiperRef.current.destroy(); }; }, []); return ( <div className="swiper-container"> <div className="swiper-wrapper"> <div className="swiper-slide">Slide 1</div> <div className="swiper-slide">Slide 2</div> <div className="swiper-slide">Slide 3</div> </div> <div className="swiper-button-prev"></div> <div className="swiper-button-next"></div> </div> ); }

5.2 集成 Chart.js

import Chart from 'chart.js/auto'; function ChartComponent({ data }) { const chartRef = useRef(null); const chartInstance = useRef(null); useEffect(() => { if (chartInstance.current) { chartInstance.current.destroy(); } chartInstance.current = new Chart(chartRef.current, { type: 'line', data: data, options: { responsive: true, maintainAspectRatio: false } }); return () => { if (chartInstance.current) { chartInstance.current.destroy(); } }; }, [data]); return <canvas ref={chartRef} />; }

六、常见陷阱与注意事项

6.1 Refs 更新时机

function RefTiming() { const ref = useRef(0); const [state, setState] = useState(0); const updateRef = () => { ref.current = ref.current + 1; console.log('ref 已更新:', ref.current); // 不会触发重新渲染 }; const updateState = () => { setState(state + 1); // 触发重新渲染 }; return ( <div> <p>Ref 值: {ref.current}(不会在 UI 更新)</p> <p>State 值: {state}</p> <button onClick={updateRef}>更新 Ref</button> <button onClick={updateState}>更新 State</button> </div> ); }

6.2 条件渲染中的 Refs

function ConditionalRef() { const [show, setShow] = useState(false); const inputRef = useRef(null); const focusInput = () => { if (inputRef.current) { inputRef.current.focus(); } }; return ( <div> <button onClick={() => setShow(!show)}>切换</button> <button onClick={focusInput}>聚焦</button> {show && <input ref={inputRef} placeholder="条件渲染的输入框" />} </div> ); }

6.3 不要在渲染期间读取 Refs

function BadExample() { const ref = useRef(null); // ❌ 错误:在渲染期间读取 ref if (ref.current) { console.log(ref.current.value); } // ✅ 正确:在事件处理或 useEffect 中读取 useEffect(() => { if (ref.current) { console.log(ref.current.value); } }, []); return <input ref={ref} />; }

七、性能优化

7.1 避免不必要的 Refs 更新

function OptimizedComponent() { const ref = useRef(null); // 使用 useCallback 避免重新创建回调 const setRef = useCallback((node) => { if (node) { // 只在节点挂载时执行一次 node.style.opacity = 1; } ref.current = node; }, []); return <div ref={setRef}>内容</div>; }

7.2 使用 ResizeObserver

function ResizeObserverExample() { const elementRef = useRef(null); const [size, setSize] = useState({ width: 0, height: 0 }); useEffect(() => { if (!elementRef.current) return; const observer = new ResizeObserver(entries => { const { width, height } = entries[0].contentRect; setSize({ width, height }); }); observer.observe(elementRef.current); return () => observer.disconnect(); }, []); return ( <div ref={elementRef} style={{ resize: 'both', overflow: 'auto', border: '1px solid #ccc' }}> 宽度: {Math.round(size.width)}px, 高度: {Math.round(size.height)}px </div> ); }

八、练习题

基础题

  1. 实现一个 Todo 列表,添加新项后自动滚动到底部
  2. 实现一个表单,提交后自动清空并聚焦到第一个输入框

进阶题

  1. 实现一个图片懒加载组件
  2. 实现一个可拖拽排序的列表

参考答案

// 1. 自动滚动列表 function AutoScrollList() { const [items, setItems] = useState([]); const [input, setInput] = useState(''); const listRef = useRef(null); const bottomRef = useRef(null); const addItem = () => { if (input.trim()) { setItems([...items, input]); setInput(''); } }; useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [items]); return ( <div> <div ref={listRef} style={{ height: '200px', overflow: 'auto', border: '1px solid #ccc' }}> {items.map((item, index) => ( <div key={index}>{item}</div> ))} <div ref={bottomRef} /> </div> <input value={input} onChange={(e) => setInput(e.target.value)} /> <button onClick={addItem}>添加</button> </div> ); }

九、小结

要点说明
useRef函数组件中创建 ref
createRef类组件中创建 ref
forwardRef转发 ref 到子组件
useImperativeHandle自定义暴露给父组件的实例值
回调 ref更精细地控制 ref 设置

核心要点

  • Refs 是操作 DOM 的逃生舱
  • 优先使用声明式方案
  • 使用 forwardRef 传递 ref
  • 注意 ref 不会触发重新渲染
  • 在 useEffect 或事件中访问 ref

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

相关文章:

  • 讲真的2026年武汉离婚律师推荐 这5位实战派值得选 - 本地品牌推荐
  • 随着树木和非树木植被覆盖的扩大,全球人口暴露于城市绿地的不平等加剧
  • 【大白话说Java面试题 第97题】【Mysql篇】第27题:说说分库与分表的设计?
  • 2026年质量好的镶件机械手/车床机械手/伺服机械手深度厂家推荐 - 品牌宣传支持者
  • 2026年口碑好的地库地坪/无机磨石地坪/混凝土地面施工/厂房地坪生产厂家推荐 - 行业平台推荐
  • 新手开店不会管水站?数字化工具助力新店平稳起步
  • 从STM32转战HC32,GPIO配置这5个坑我帮你踩过了(含解锁、等待时间、复用功能避坑)
  • GRB X射线吸收研究:TEPID模型与介质特性分析
  • 告别接线混乱!ESP8266驱动1.44寸ST7735屏,TFT_eSPI库的OVERLAP模式实战(附完整代码)
  • 从‘边缘’到‘语义’:手把手教你用TensorBoard逐层可视化ResNet的‘认知’过程(PyTorch版)
  • 告别原生File类:用Hutool的FileUtil,5分钟搞定Java文件操作(附避坑指南)
  • 【C++初阶】STL 开篇:站在巨人肩膀上,先聊聊编码和现代语法
  • 入门大模型工程师第五课----通过微调改善大模型在垂直领域的表现
  • STM32CubeMX配置USART空闲中断+DMA接收不定长数据,5分钟搞定(HAL库版)
  • Speechless终极指南:3分钟学会微博备份,永久保存你的数字记忆
  • 保姆级教程:用ROS1在局域网内搞定两台机器人的主从通信(含rqt_graph可视化验证)
  • 基于小程序的医疗报销系统的设计与实现毕业设计源码
  • 别只看天梯图了!用这套“需求-预算”匹配法,5分钟搞定你的第一台游戏主机
  • 增强现实眼镜公司US Orange Inc聘请顾问为纳斯达克IPO做准备
  • 毕业季论文攻坚利器:百考通AI,一站式解决本硕博论文全流程难题
  • VS Code + Cursor + Continue + Warp + LangChain + Ollama —— 这套组合为何让资深工程师日均编码时长缩短2.8小时?
  • 2026市政领域诚信一体化废水处理设备推荐榜 - 优质品牌商家
  • 别再迷信软件了!用Python自己算筹码获利比(Winner函数),避免数据黑箱
  • 2026年热门的双臂机械手/三轴机械手推荐品牌厂家 - 行业平台推荐
  • SpringBoot项目升级Swagger3.0后,swagger-ui.html 404?别慌,一个注解和依赖就搞定
  • 从功能块到Case语句:手把手教你用CODESYS ST语言编写电机运动控制程序
  • 达州新高考志愿填报机构评测:四川老牌志愿填报机构哪家懂新高考/本土头部机构的硬核实力对比 - 优质品牌商家
  • UDS服务0x19到底做了什么?为什么一个ReadDTCInformation请求能把DEM全部串起来?
  • Meta:智能体自主发现高效混合架构
  • 从NLP到CV:手把手教你用PyTorch复现Vision Transformer(ViT)图像分类模型