You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中useEffect替代用法及滚动霓虹线报错排查

Next.js中useEffect的差异及滚动霓虹线失效问题排查

一、Next.js中useEffect的特殊用法

Next.js因支持SSR/SSG渲染模式,useEffect的使用和纯React SPA有两点核心差异:

  • App Router(Next.js 13+):组件默认是服务器组件,服务器组件不支持useEffect、useRef等客户端钩子,必须在组件顶部添加'use client'指令,才能启用客户端钩子的功能。
  • Pages Router:页面级组件的useEffect只会在客户端hydration(水合)完成后执行,SSR阶段不会触发,这和纯React SPA一致,但要注意hydration期间的DOM就绪时机。

二、霓虹线报错"neonLine is null"的原因及修复

核心原因分析

  1. CSS Modules类名哈希化:你使用了CSS Modules,Next.js会自动将styles["nLine"]编译成带哈希的类名(比如nLine_xyz123),而代码中用document.querySelector(".nLine")查找原始类名,自然找不到元素。
  2. DOM未就绪时触发scroll事件:Next.js SSR后hydration阶段,若页面初始滚动位置非顶部,scroll事件可能在DOM完全挂载前触发,此时document.querySelector无法找到目标元素。
  3. 闭包导致的状态捕获问题:原代码useEffect依赖为空数组,checkScrollStop中读取的scrolling始终是初始值false,逻辑完全失效。

修复后的完整代码

import React, { useState, useEffect, useRef } from "react";
import styles from "./Neon.module.css";

// App Router下必须添加该指令,Pages Router可省略
'use client'

const Neon = () => {
  const [scrolling, setScrolling] = useState(false);
  const neonLineRef = useRef(null); // 用React ref获取DOM元素,替代querySelector

  useEffect(() => {
    const handleScroll = () => {
      setScrolling(true);
      // 先判断元素是否存在,避免null报错
      if (!neonLineRef.current) return;
      
      const scrollTop = window.scrollY;
      const windowHeight = window.innerHeight;
      const documentHeight = document.documentElement.scrollHeight;
      const scrollPercentage = (scrollTop / (documentHeight - windowHeight)) * 100;
      neonLineRef.current.style.height = `${scrollPercentage}%`;
    };

    let scrollingTimeout;
    const checkScrollStop = () => {
      if (scrolling) {
        setScrolling(false);
        clearTimeout(scrollingTimeout);
      } else {
        if (!neonLineRef.current) return;
        neonLineRef.current.style.transition = "height 0.5s ease-in-out";
      }
    };

    // 给滚动事件加防抖,减少DOM操作频率,避免初始阶段频繁触发
    const debounceHandleScroll = (...args) => {
      clearTimeout(scrollingTimeout);
      scrollingTimeout = setTimeout(() => handleScroll(...args), 10);
    };

    window.addEventListener("scroll", debounceHandleScroll);
    window.addEventListener("scroll", checkScrollStop);

    return () => {
      window.removeEventListener("scroll", debounceHandleScroll);
      window.removeEventListener("scroll", checkScrollStop);
    };
  }, [scrolling]); // 将scrolling加入依赖,解决闭包状态捕获问题

  return (
    <div className={styles.con}>
      <div className={styles.neon}></div>
      <div className={styles.line}>
        {/* 将ref绑定到目标元素 */}
        <div ref={neonLineRef} className={styles["nLine"]}></div>
      </div>
    </div>
  );
};

export default Neon;

关键修复点说明

  1. 用React ref替代document.querySelector:直接绑定元素ref,避免CSS Modules类名哈希化导致的查找失败,同时符合React生命周期规范。
  2. 添加元素存在校验:每次操作DOM前先判断neonLineRef.current是否存在,防止null报错。
  3. 修复闭包问题:将scrolling加入useEffect依赖数组,确保回调能获取最新状态。
  4. 滚动事件防抖:降低scroll事件触发频率,提升性能的同时避免初始阶段DOM未就绪时的频繁调用。
  5. 适配App Router:添加'use client'指令,确保客户端钩子能正常运行。

内容的提问来源于stack exchange,提问作者omarN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 09:25:29