在 React 中,可以使用原生 DOM 操作,但要谨慎使用 。因为 React 本身通过 Virtual DOM(虚拟DOM) 来管理页面,如果直接操作真实 DOM,可能会与 React 的状态管理机制产生冲突。
一、为什么 React 不推荐直接操作 DOM
传统 JavaScript:
ini
const div = document.getElementById('box');
div.innerHTML = 'Hello';
div.style.color = 'red';
React:
javascript
function App() {
const [text, setText] = useState('Hello');
return (
<div>
<div>{text}</div>
<button onClick={() => setText('World')}>
修改
</button>
</div>
);
}
React 的核心思想:
sql
State
↓
Virtual DOM
↓
Real DOM
如果你直接修改:
ini
document.getElementById('box').innerHTML = '123';
React 下一次渲染时:
scss
setText('World');
React 会重新生成 DOM。
结果:
你的修改可能被覆盖
二、React中操作DOM的正确方式
1. 使用 useRef(最推荐)
获取DOM
javascript
import { useRef } from 'react';
function App() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
聚焦
</button>
</>
);
}
相当于
原生:
dart
document.querySelector('input');
React:
inputRef.current
2. 获取元素尺寸
开发中经常遇到:
获取宽高
获取位置
滚动距离
offsetWidth
ini
const divRef = useRef();
useEffect(() => {
console.log(divRef.current.offsetWidth);
}, []);
xml
<div ref={divRef}>内容</div>
getBoundingClientRect
arduino
useEffect(() => {
const rect = divRef.current.getBoundingClientRect();
console.log(rect.width);
console.log(rect.height);
console.log(rect.top);
console.log(rect.left);
}, []);
三、React中监听DOM事件
错误写法
scss
useEffect(() => {
document
.getElementById('btn')
.addEventListener('click', fn);
}, []);
容易造成:
事件重复绑定
内存泄漏
正确写法
React事件系统:
xml
<button onClick={handleClick}>
点击
</button>
如果必须监听
例如:
javascript
window
document
scroll
resize
使用:
javascript
useEffect(() => {
const fn = () => {
console.log('resize');
};
window.addEventListener('resize', fn);
return () => {
window.removeEventListener('resize', fn);
};
}, []);
四、操作样式注意事项
错误
ini
divRef.current.style.color = 'red';
React重新渲染:
scss
setState(...)
可能被覆盖。
推荐
State控制
javascript
const [color, setColor] = useState('red');
return (
<div style={{ color }}>
hello
</div>
);
Class控制
css
<div className={isActive ? 'active' : ''}>
内容
</div>
五、操作表单注意事项
错误
ini
input.value = '张三';
React推荐
ini
const [name, setName] = useState('');
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
这叫:
受控组件(Controlled Component)
六、操作滚动条
这是 React 中最常见的 DOM 操作之一。
滚动到顶部
css
window.scrollTo({
top: 0,
behavior: 'smooth',
});
指定元素滚动
ini
divRef.current.scrollTop = 100;
滚动到某个元素
css
divRef.current.scrollIntoView({
behavior: 'smooth',
});
七、React StrictMode 注意事项
React18 开发模式下:
xml
<React.StrictMode>
<App />
</React.StrictMode>
会导致:
scss
useEffect(() => {
console.log('执行');
}, []);
输出:
执行
执行
原因:
开发模式故意执行两次
检测副作用
因此 DOM 操作要避免:
创建两次
绑定两次
请求两次
例如:
scss
useEffect(() => {
const timer = setInterval(() => {}, 1000);
return () => {
clearInterval(timer);
};
}, []);
一定要写清理函数。
八、React中绝对不要这样做
1. 直接修改React管理的DOM
ini
document.getElementById('root').innerHTML =
'<h1>Hello</h1>';
React会失控。
2. 使用jQuery操作React节点
javascript
$('#box').hide();
React重新渲染后:
状态不同步
3. 在 render 中操作DOM
错误:
javascript
function App() {
document.title = '首页';
return <div>hello</div>;
}
应该:
ini
useEffect(() => {
document.title = '首页';
}, []);
九、React中必须操作DOM的场景
以下场景允许且常见:
| 场景 | 方法 |
|---|---|
| 输入框聚焦 | ref.focus() |
| 获取宽高 | offsetWidth |
| 获取位置 | getBoundingClientRect |
| 滚动控制 | scrollTop |
| Canvas绘图 | canvasRef |
| 视频播放 | video.play() |
| 音频播放 | audio.play() |
| 第三方库集成 | ECharts、Mapbox、Three.js |
| 文件上传 | inputRef.current.click() |
| 拖拽功能 | drag/drop API |
| IntersectionObserver | ref + observer |
十、前端面试高频回答
如果面试官问:
React 中为什么不推荐直接操作 DOM?
可以回答:
React 采用 Virtual DOM 和单向数据流机制,页面应由 State 驱动。如果直接操作真实 DOM,会绕过 React 的更新流程,导致状态与视图不一致,甚至被 React 的重新渲染覆盖。因此 React 推荐通过 state、props 控制 UI,仅在获取节点、聚焦、滚动、获取尺寸、集成第三方库等场景下,通过 useRef 和 useEffect 操作 DOM。
这是中高级 React 开发面试中关于 DOM 操作最标准的回答。