
本文解释了 React 中 useState 触发组件重渲染的机制,以及为何在 useEffect 中调用 setTest(scrollY) 后,console.log(scrollY) 能在每次滚动时输出最新值——关键在于重渲染使函数组件体重新执行,从而读取到 DOM 当前的 scrollY。
本文解释了 react 中 `usestate` 触发组件重渲染的机制,以及为何在 `useeffect` 中调用 `settest(scrolly)` 后,`console.log(scrolly)` 能在每次滚动时输出最新值——关键在于重渲染使函数组件体重新执行,从而读取到 dom 当前的 `scrolly`。
在 React 函数组件中,组件体(即 JSX 返回前的所有 JavaScript 代码)会在每次渲染时重新执行。这意味着其中访问的任何变量(如 scrollY)都会被实时求值,而非“缓存”为初始值。
回到你的代码:
const [test, setTest] = useState(0);
useEffect(() => {
function handleScroll() {
setTest(window.scrollY); // ? 触发状态更新 → 引发重渲染
}
handleScroll(); // 初始执行一次
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []); // 空依赖数组:仅挂载时执行
// ✅ 每次重渲染都会重新执行这行:
console.log(window.scrollY); // 输出当前滚动位置(非旧值!)? 关键机制解析
- setTest(window.scrollY) 并不直接“保存”或“传递” scrollY 给 test;它只是发起一次状态更新请求。
- React 接收到该请求后,会计划一次新的渲染(re-render)。
- 新渲染开始时,整个组件函数体重新运行,console.log(window.scrollY) 再次执行 —— 此时 window.scrollY 是滚动事件触发后的最新值(例如 327),因此你看到的是实时滚动位置。
⚠️ 注意:scrollY 是一个动态只读属性(window.scrollY),每次访问都返回当前滚动偏移量。它不是 React state,也不受 useState 管理 —— 它只是你在渲染过程中“现场读取”的 DOM 值。
❌ 为什么注释掉 setTest 就只打印一次?
若删除 setTest(window.scrollY),useEffect 内部虽仍注册了滚动监听器,但没有触发任何状态变更,React 不会主动重渲染组件。因此组件仅在初始挂载时渲染一次,console.log(window.scrollY) 也只执行一次(通常是 0)。
✅ 正确实践建议(避免副作用滥用)
虽然上述逻辑可行,但在 JSX 中写 console.log 属于副作用,且违背 React 渲染纯函数原则。更规范的做法是:
useEffect(() => {
const handleScroll = () => {
console.log(window.scrollY); // ✅ 在 effect 内处理副作用
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);或者,若需将滚动值用于状态或后续逻辑:
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
const handleScroll = () => setScrollY(window.scrollY);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// 现在 scrollY 是可响应的 React state,可用于条件渲染、动画等
return <div>Scrolled to: {scrollY}px</div>;✅ 总结
- useState 本身不“代理”或“监听”外部变量;它的作用是声明状态并驱动重渲染。
- console.log(scrollY) 能持续输出新值,本质是因为 setXXX 触发了重渲染,使组件体反复执行,从而每次都读取最新的 window.scrollY。
- 真正的响应式数据应通过 useState + useEffect 显式同步,而非依赖副作用式日志来“观察”变化。
- 避免在渲染体中放置副作用(如 console.log、API 调用),应移入 useEffect 或其他专用 Hook 中管理。










