React响应式布局开发中useEffect报Cannot read property 'style' of undefined错误
问题原因
你遇到的Cannot read property 'style' of undefined报错核心原因是DOM节点不存在时强行读取了节点的style属性,具体触发场景有三个:
- 你绑定了
ref的<main>节点仅在宽度>650时渲染,当页面宽度<=650时,ref.current为null,此时第三个useEffect直接读取ref.current.children就会报错,后续滚动事件触发时读取不存在的节点样式也会报错 - 宽度判断逻辑存在1px的空档:宽度等于650时,两类视图都不会渲染,此时
ref.current同样为null - 滚动事件绑定后没有做节点存在性校验,即使对应DOM已经被销毁,滚动事件触发时依然会尝试修改节点样式
修复方案
按以下步骤修改即可解决问题:
- 合并冗余的宽度状态,将
screen和width合并为一个状态存储窗口宽度 - 修复宽度判断的1px空档,统一判断逻辑
- 所有操作DOM的位置前先校验节点是否存在
- 给初始化获取宽度的useEffect添加空依赖,避免重复执行
- 滚动事件回调中增加节点存在性校验,节点不存在时终止逻辑执行
修正后核心代码
import { useState, useEffect, useRef } from 'react'; export default function App() { // 服务端渲染兼容处理,纯客户端项目可直接写window.innerWidth const [width, setWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : 0); const ref = useRef(null); const setImageLimitMovement = 1; const setTextLimitMovement = 4; const opacityRange = 400; const speed = 1; // 监听窗口宽度变化 useEffect(() => { const handleResize = () => { setWidth(window.innerWidth); }; window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); // 滚动动画逻辑 useEffect(() => { // 节点不存在时直接终止逻辑 if (!ref.current) return; const app = [...ref.current.children]; const titles = app.filter((el) => el.matches(".titles")); const blocks = app.filter((el) => el.matches(".blocks")); const img = app.find((el) => el.matches("#passport")); // 校验需要用到的节点是否都存在 if (!titles[0] || !titles[1] || !img || !blocks[0]) return; const centerPoints = blocks.map((blockEl, idx) => { const blockindex = idx + 1; const blockHeight = Math.floor(blockEl.getBoundingClientRect().height); const blockHalf = blockHeight / 2; return blockHeight * blockindex - blockHalf; }); const changeBackground = () => { // 每次执行滚动逻辑前都校验节点存在性 if (!titles[0] || !titles[1] || !img || !centerPoints[0]) return; const value = window.scrollY; titles[0].style.transform = `translateY(-${value * speed}px)`; if (centerPoints[0] > value) { img.style.transform = `translateX(-${value * (1 / setImageLimitMovement)}px)`; titles[1].style.transform = `translateX( ${0 + value / setTextLimitMovement}px)`; titles[1].style.opacity = value / opacityRange; return; } window.requestAnimationFrame(changeBackground); }; window.addEventListener("scroll", changeBackground); return () => window.removeEventListener("scroll", changeBackground); }, [width]); // 依赖改为width,宽度变化时重新绑定逻辑 return ( <> {width >= 650 ? ( <div id="section2"> <main ref={ref}> <h1 id="title" className="titles"> {posts.Title} </h1> <section id="block1" className="blocks"></section> <figure id="passport"> <img alt="passport" src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg" /> </figure> <h2 id="text1" className="titles text1"> Random Text 1 </h2> </main> </div> ) : ( <div style={{ height: "100vh", backgroundColor: "black" }}></div> )} </> ); }
内容的提问来源于stack exchange,提问作者Jay Modi
相关产品推荐
相关产品推荐

