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

React主题切换时用useState更新Image组件的问题求助

问题

React新手搭建作品集网站,需求是点击Switcher组件的主题切换按钮时,Home组件中的图片同步切换。使用Tailwind CSS,切换按钮时会给html元素添加/移除dark类。当前实现能切换图片,但控制台抛出「Maximum update depth exceeded」警告,提示Home.js中Hook使用不规范,需要优化。

控制台警告信息:

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
at Home (http://localhost:3000/main.428ce48dfed5fa0fec8a.hot-update.js:38:74)
at div
at App

优化方案

核心问题

Home组件里的useEffect未设置依赖数组,导致每次组件渲染都会触发该effect,而effect内调用setImgg又会触发组件重新渲染,形成无限循环,进而触发更新深度超限警告。此外,直接将JSX元素存入state并非React最佳实践,应仅存储数据,渲染时再生成JSX。

具体优化方案

方案一:状态提升(推荐)

将主题状态(isDark)放到父组件(如App)中,通过props传递给Home和Switcher,实现状态统一管理,避免直接操作DOM,彻底解决无限循环问题。

修改后的App.js(父组件示例)

import React, { useState, useEffect } from 'react';
import Home from './Home';
import Switcher from './Switcher';

function App() {
  const [isDark, setIsDark] = useState(true);

  // 同步html的dark类
  useEffect(() => {
    const html = document.querySelector('html');
    isDark ? html.classList.add('dark') : html.classList.remove('dark');
  }, [isDark]);

  const toggleTheme = () => setIsDark(prev => !prev);

  return (
    <div className="App">
      <Switcher isDark={isDark} onToggle={toggleTheme} />
      <Home isDark={isDark} />
    </div>
  );
}

export default App;

修改后的Home.js

import React from 'react';
import { AiFillGithub } from "react-icons/ai";
import { SiFacebook, SiCodepen, SiLinkedin } from "react-icons/si";
import darkImg from "../images/bg.jpg";
import lightImg from "../images/bg-light.jpg";

function Home({ isDark }) {
  // 根据状态直接选择图片,无需存储JSX到state
  const currentImg = isDark ? darkImg : lightImg;

  return (
    <div className="h-full mt-[2px] flex-row">
      <img className="ml-14" src={currentImg} alt="home" />
      <div className="font-arvo tracking-wider text-center text-7xl">PORTFOLIO</div>
      <div className="text-center text-4xl">Software Engineer</div>
      <div className="flex flex-row items-center justify-center mt-5 space-x-7">
        <a href="#"><AiFillGithub className="social-icons" size="30"/></a>
        <a href="#"><SiFacebook className="social-icons" size="30"/></a>
        <a href="#"><SiCodepen className="social-icons" size="30"/></a>
        <a href="#"><SiLinkedin className="social-icons" size="30"/></a>
      </div>
    </div>
  );
}

export default Home;

修改后的Switcher.js

import React from 'react';
import { HiLightBulb } from "react-icons/hi";
import { MdDarkMode } from "react-icons/md";

function Switcher({ isDark, onToggle }) {
  // 根据状态动态计算样式和图标,无需存储到state
  const decorStyle = isDark 
    ? "flex flex-row w-6 h-6 md:w-7 md:h-7 relative rounded-full transition duration-500 transform bg-two -translate-x-2 pt-1 pl-0.5 text-black dark:bg-nine dark:text-seven"
    : "flex flex-row w-6 h-6 md:w-7 md:h-7 relative rounded-full transition duration-500 transform bg-two translate-x-full pt-1 pl-0.5 text-black dark:bg-nine dark:text-seven";

  const currentIcon = isDark ? <MdDarkMode size="20"/> : <HiLightBulb size="20"/>;

  return (
    <Toggler decor={decorStyle} icon={currentIcon} onToggle={onToggle}/>
  );
}

const Toggler = (props) => {
  return (
    <button onClick={props.onToggle} className="absolute top-9 right-10 w-14 h-5 rounded-2xl flex items-center transition duration-300 focus:outline-none shadow bg-four dark:bg-white">
      <div id="switch-toggle" className={props.decor}>
        {props.icon}
      </div>
    </button>
  );
};

export default Switcher;

方案二:不提升状态,修复Home组件无限循环

若不想修改组件结构,可给Home的useEffect添加空依赖数组,同时用MutationObserver监听html的class变化,仅在组件挂载时初始化一次,之后仅在class变化时更新图片。

修改后的Home.js

import React, { useEffect, useState } from 'react';
import { AiFillGithub } from "react-icons/ai";
import { SiFacebook, SiCodepen, SiLinkedin } from "react-icons/si";
import darkImg from "../images/bg.jpg";
import lightImg from "../images/bg-light.jpg";

function Home(props) {
  const html = document.querySelector("html");
  const [isDark, setIsDark] = useState(html.classList.contains("dark"));

  useEffect(() => {
    // 更新图片的逻辑
    const updateImage = () => {
      setIsDark(html.classList.contains("dark"));
    };

    // 监听html的class属性变化
    const observer = new MutationObserver(updateImage);
    observer.observe(html, { attributes: true, attributeFilter: ['class'] });

    // 组件卸载时清理监听
    return () => observer.disconnect();
  }, []); // 空依赖数组,仅在组件挂载时执行一次

  return (
    <div className="h-full mt-[2px] flex-row">
      <img className="ml-14" src={isDark ? darkImg : lightImg} alt="home" />
      <div className="font-arvo tracking-wider text-center text-7xl">PORTFOLIO</div>
      <div className="text-center text-4xl">Software Engineer</div>
      <div className="flex flex-row items-center justify-center mt-5 space-x-7">
        <a href="#"><AiFillGithub className="social-icons" size="30"/></a>
        <a href="#"><SiFacebook className="social-icons" size="30"/></a>
        <a href="#"><SiCodepen className="social-icons" size="30"/></a>
        <a href="#"><SiLinkedin className="social-icons" size="30"/></a>
      </div>
    </div>
  );
}

export default Home;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 01:06:30