
本文详解如何修复因 useEffect 缺少依赖数组导致的 3D 模型频繁重载问题,通过正确配置 React Hooks 和 Canvas 渲染逻辑,确保 GLTF 模型仅初始化一次、稳定驻留在 DOM 中。
本文详解如何修复因 `useeffect` 缺少依赖数组导致的 3d 模型频繁重载问题,通过正确配置 react hooks 和 canvas 渲染逻辑,确保 gltf 模型仅初始化一次、稳定驻留在 dom 中。
在使用 @react-three/fiber + @react-three/drei 构建滚动驱动的 3D 场景(如可拖拽冰箱模型)时,一个常见但隐蔽的问题是:模型随页面滚动反复卸载-重载,表现为视觉闪烁、材质重置、性能骤降——正如视频中所见的“刷新”现象。
根本原因在于代码中两个关键 useEffect 钩子遗漏了空依赖数组 []:
// ❌ 错误写法:无依赖数组 → 每次渲染都执行,触发 Canvas 重挂载
useEffect(() => {
const handleScroll = () => setScrollY(window.scrollY);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
});
useEffect(() => {
const fridge = document.getElementById('fridge');
window.addEventListener('scroll', () => {
for (let i = 0; i <p>当 useEffect 不传第二个参数(依赖数组)时,它会在<strong>每次组件重渲染时都执行</strong>。而 scrollY 是 useState 状态,其更新会触发重渲染;重渲染又触发 useEffect 再次绑定事件监听器(甚至可能叠加多个监听器),更严重的是:<canvas> 组件被反复销毁重建,导致内部 useGLTF 卸载并重新加载模型——这就是模型“刷新”的技术根源。</canvas></p><p>✅ 正确做法是显式传入空数组 [],确保副作用<strong>仅在组件挂载时执行一次</strong>:</p><pre class="brush:php;toolbar:false;">useEffect(() => {
const handleScroll = () => setScrollY(window.scrollY);
handleScroll(); // 初始化读取当前滚动位置
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []); // ✅ 添加空依赖数组
useEffect(() => {
const fridge = document.getElementById('fridge');
const handleScroll = () => {
// ⚠️ 注意:此处逻辑仍有性能问题(见下方说明)
fridge.style.transform = `translateX(${-scrollY}px)`;
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []); // ✅ 添加空依赖数组
此外,原代码中 for 循环更新 transform 的方式存在严重性能缺陷:每帧滚动都执行 scrollY 次 DOM 操作,且未做防抖/节流。应直接基于当前 scrollY 值计算位移:
// ✅ 优化:单次赋值 + requestAnimationFrame 兼容性更好
useEffect(() => {
const fridge = document.getElementById('fridge');
const handleScroll = () => {
fridge.style.transform = `translateX(${ -scrollY }px)`;
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [scrollY]); // 若需响应 scrollY 变化,可将 scrollY 加入依赖(但通常用 ref 更优)
? 关键建议:对滚动相关的 DOM 更新,推荐使用 useRef + useFrame 或 requestAnimationFrame 进行高性能同步,避免直接在 scroll 事件中操作样式。但在本场景中,仅修复 useEffect 依赖即可根治模型重载问题。
最后,请确保模型预加载已生效:
useGLTF.preload("modern_fridge.glb"); // ✅ 在组件外调用,提前缓存
这能避免首次渲染时的加载延迟,并配合 Suspense 提供优雅的加载态。
总结:
- 必做:所有仅需初始化执行的 useEffect 必须添加 [] 依赖数组;
- 推荐:用 ref 管理 DOM 元素而非 getElementById;
- 进阶:将滚动位移逻辑迁移至 useFrame 或 useScroll Hook,实现与渲染帧率同步,彻底消除卡顿与重载风险。
模型不再闪烁,交互丝滑如初——这才是声明式 3D Web 应用该有的稳定性。











