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

使用React应用条件CSS时出现渲染延迟问题求助

React条件CSS样式应用延迟问题排查与解决

问题现象

在React小型项目中,当游戏计时结束触发handleTimesUp、将状态更新为timesup时,按钮文字会立刻切换为"Time's up!",但对应的红色加粗CSS样式却存在数毫秒的可见延迟,短暂时间内文字仍保持原有颜色。

相关代码

Word组件

export default function Word({ word, state, onClick }) {
  return (
    <button
      id='word'
      disabled={state !== 'gameover'}
      onClick={onClick}
      className={state === 'timesup' ? 'timesup' : ''}
      >
      {state === 'playing' && word}
      {state === 'gameover' && 'GO'}
      {state === 'timesup' && 'Time\'s up!'}
    </button>
  )
}

App.js

import { useState } from 'react';

import Timer from './components/Timer';
import Word from './components/Word';

function App() {
  const timer = 3;
  const [word, setWord] = useState('some word');
  const [state, setState] = useState('gameover');

  const handleStartGame = () => {
    setState('playing');
  }

  const handleTimesUp = async event => {
    setState('timesup');
    await sleep(3000)
    setState('gameover');
  }

  const sleep = ms => new Promise(
    resolve => setTimeout(resolve, ms)
  )

  return (
    <>
      <Timer duration={timer} isRunning={state === 'playing'} timesUp={handleTimesUp} />
      <Word word={word} state={state} onClick={handleStartGame}/>
    </>
  );
}

export default App;

Timer.js

import { useState, useEffect } from "react"

export default function Timer({ duration, isRunning, timesUp }) {
  const [time, setTime] = useState()

  useEffect(() => {
    if (isRunning) {
      const timer = time > 0 && setInterval(() => setTime(time - 1), 1000)
      if (time === 0) {
        timesUp()
      }
      return () => clearInterval(timer)
    } else {
      setTime(duration)
    }
  }, [time, isRunning])

  return (
    <div id='timer'>{time}</div>
  )
}

CSS样式

#word {
  font-size: 20px;
  font-weight: 500;
  border: 1px solid var(--color-3);
  padding: 20px;
  text-align: center;
  background-color: var(--color-5);
  margin-bottom: 20px;
  color: var(--color-2);
  width: 100%;
  height: 90px;
}

#word:disabled {
  color: var(--color-2);
  pointer-events: none;
}

#word.timesup {
  color: red;
  font-weight: 800;
}

问题原因

核心问题是CSS选择器优先级冲突与样式应用顺序:

  1. 当状态切换为timesup时,按钮的disabled属性会变为true(因为state !== 'gameover'为真),同时className会加上timesup。
  2. #word:disabled和#word.timesup的CSS优先级相同(ID选择器优先级100 + 伪类/类选择器优先级10,总计110)。
  3. 浏览器在处理DOM属性更新时,可能先应用disabled状态对应的样式,再应用新增的类样式,导致短暂的样式延迟;或者因优先级相同,先加载的#word:disabled样式暂时覆盖后加载的#word.timesup样式,直到浏览器重新计算样式。

解决办法

方法1:提高目标样式的优先级

修改timesup样式的选择器,将其与disabled伪类结合,提升优先级:

#word.timesup:disabled {
  color: red;
  font-weight: 800;
}

该选择器优先级为ID(100) + 类(10) + 伪类(10) = 120,高于单独的#word:disabled,确保状态切换时样式立刻生效。

方法2:调整样式声明顺序

如果不想修改选择器,可将#word.timesup的样式声明移到#word:disabled之后:

#word:disabled {
  color: var(--color-2);
  pointer-events: none;
}

#word.timesup {
  color: red;
  font-weight: 800;
}

(注:此方法依赖浏览器对同优先级样式的“后声明优先”规则,稳定性不如方法1)

方法3:修复Timer组件的逻辑漏洞

Timer组件存在潜在问题:当time初始化为undefined时,第一次进入isRunning状态可能不会正确启动计时。调整初始化逻辑确保time初始值正确:

export default function Timer({ duration, isRunning, timesUp }) {
  const [time, setTime] = useState(duration) // 直接初始化为传入的duration

  useEffect(() => {
    let interval;
    if (isRunning) {
      interval = setInterval(() => {
        setTime(prevTime => {
          const newTime = prevTime - 1;
          if (newTime === 0) {
            clearInterval(interval);
            timesUp();
          }
          return newTime;
        });
      }, 1000);
    } else {
      setTime(duration);
    }
    return () => clearInterval(interval);
  }, [isRunning, duration, timesUp])

  return (
    <div id='timer'>{time}</div>
  )
}

这个修复确保计时逻辑更可靠,避免因计时触发时机异常导致的状态更新延迟。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 04:52:53