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

张开发
2026/6/6 4:01:13 15 分钟阅读

分享文章

01-React基础入门——11-Refs 与 DOM 操作
Refs 与 DOM 操作一、Refs 概述1.1 什么是 RefsRefs 是 React 提供的逃生舱用于直接访问 DOM 元素或组件实例。在大多数情况下应该使用声明式的 React 数据流但有些场景必须直接操作 DOM。1.2 何时使用 Refs场景是否推荐使用 Refs管理焦点、文本选择✅ 推荐媒体播放控制video/audio✅ 推荐触发强制动画✅ 推荐集成第三方 DOM 库✅ 推荐获取元素尺寸位置✅ 推荐通过 props 传递数据❌ 应使用 state触发状态更新❌ 应使用 state二、创建和使用 Refs2.1 useRef Hook函数组件function TextInputWithFocus() { const inputRef useRef(null); const focusInput () { inputRef.current.focus(); }; return ( div input ref{inputRef} typetext 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} typetext / button onClick{this.focusInput}聚焦/button /div ); } }2.3 回调 Refsfunction 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} typepassword 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} width100% 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} classNamefancy-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 转发多个 Refsconst 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 useImperativeHandleconst 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 集成 Swiperimport 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 classNameswiper-container div classNameswiper-wrapper div classNameswiper-slideSlide 1/div div classNameswiper-slideSlide 2/div div classNameswiper-slideSlide 3/div /div div classNameswiper-button-prev/div div classNameswiper-button-next/div /div ); }5.2 集成 Chart.jsimport 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 pRef 值: {ref.current}不会在 UI 更新/p pState 值: {state}/p button onClick{updateRef}更新 Ref/button button onClick{updateState}更新 State/button /div ); }6.2 条件渲染中的 Refsfunction 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 不要在渲染期间读取 Refsfunction 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 使用 ResizeObserverfunction 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 ); }八、练习题基础题实现一个 Todo 列表添加新项后自动滚动到底部实现一个表单提交后自动清空并聚焦到第一个输入框进阶题实现一个图片懒加载组件实现一个可拖拽排序的列表参考答案// 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函数组件中创建 refcreateRef类组件中创建 refforwardRef转发 ref 到子组件useImperativeHandle自定义暴露给父组件的实例值回调 ref更精细地控制 ref 设置核心要点Refs 是操作 DOM 的逃生舱优先使用声明式方案使用 forwardRef 传递 ref注意 ref 不会触发重新渲染在 useEffect 或事件中访问 ref

更多文章