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

Astro+Preact中useEffect未触发,计时卡停滞在0的问题求助

问题描述

我正在使用Astro和Preact开发项目,需要实现一个每秒更新的戒烟时长统计卡片,起始日期存储在localStorage中。使用普通JS脚本时功能正常,但通过useEffect Hook实现时,计时器始终停留在0,无法正常更新。

相关代码如下:

index.astro

---
import 'bootstrap-icons/font/bootstrap-icons.css';
import BaseLayout from '../layouts/BaseLayout.astro';
import FunFact from '../components/FunFact.astro';
import Card from '../components/Card';
import SmokeFreeTimeCard from '../components/SmokeFreeTimeCard';
const pageTitle = "Übersicht";
---
<BaseLayout pageTitle={pageTitle}>

    <Card cardTitle="Funfact" iconClass="fa-regular fa-face-laugh">
        <FunFact />
    </Card>

    <SmokeFreeTimeCard cardTitle="Rauchfreie Zeit" iconClass="fa-solid fa-ban-smoking">
            
    </SmokeFreeTimeCard>

    <Card cardTitle="Finanzen" iconClass="fa-solid fa-money-bill">
        Du hast bereits <b>X€</b> gespart!<br/>
        Dein jähliches Ersparnis beläuft sich auf <b>XXXX€</b>. Das sind <b>XXX€</b> im Monat bzw. <b>XX€</b> am Tag!
    </Card>

    <Card cardTitle="Konsum" iconClass="fa-solid fa-wand-magic-sparkles">
        Du hast bereits <b>XX,XX Zigaretten</b> widerstanden.<br/>
        Auf ein Jahr gerechnet sind das <b>XX.XXX Stück</b>
    </Card>

    <script>
        import "../scripts/index.js";
    </script>
</BaseLayout>

Card.jsx

import 'bootstrap/dist/css/bootstrap.min.css';
import '@fortawesome/fontawesome-free/css/all.min.css';

const Card = ({ cardTitle, iconClass, children }) => (
  <div class="card" style={{ marginBottom: '20px' }}>
    <div class="card-header" style={{ color: '#ACEB98' }}>
      <i class={iconClass}></i> <b>{cardTitle}</b>
    </div>
    <div class="card-body">
      <p class="card-text">
        {children} {/* Hier werden die eingefügten Texte gerendert */}
      </p>
    </div>
  </div>
);

export default Card;

SmokeFreeTimeCard.jsx

import { useState, useEffect } from 'preact/hooks';
import Card from '../components/Card';

const SmokeFreeTimeCard = ({ cardTitle, iconClass }) => {
  const [timeElapsed, setTimeElapsed] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      const dateOfReturn = localStorage.getItem('dateOfReturn');
      console.log('Date of Return:', dateOfReturn); // Überprüfe das Datum im localStorage
      if (dateOfReturn) {
        const startDate = new Date(dateOfReturn);
        const currentDate = new Date();
        const elapsedMilliseconds = currentDate - startDate;
        console.log('Elapsed Milliseconds:', elapsedMilliseconds); // Überprüfe die vergangene Zeit
        setTimeElapsed(elapsedMilliseconds);
      }
    }, 1000);
  
    return () => clearInterval(interval);
  }, []);

  const formatTime = (milliseconds) => {
    const seconds = Math.floor(milliseconds / 1000);
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const remainingSeconds = seconds % 60;
    return `${hours} Stunden ${minutes} Minuten ${remainingSeconds} Sekunden`;
  };

  return (
    <Card cardTitle={cardTitle} iconClass={iconClass}>
        {typeof timeElapsed === 'number' && (
            <>Du bist <b>{formatTime(timeElapsed)}</b> rauchfrei! Weiter so!</>
          )}
    </Card>
  );
};

export default SmokeFreeTimeCard;

问题截图:
计时器停留在0的截图


修复方案

核心问题

Astro默认对Preact组件采用静态渲染,组件的交互逻辑(比如useEffect里的定时器)不会在客户端浏览器中执行,导致timeElapsed始终保持初始值0。

具体修复步骤

  1. 激活客户端组件:在调用SmokeFreeTimeCard时添加client:load指令,强制Astro在客户端加载并激活组件的交互功能。
  2. 优化定时器逻辑:避免每秒重复读取localStorage,初始化时读取一次起始日期,之后仅计算时间差,提升性能。

修正后的代码

1. 修改index.astro中的组件调用

<SmokeFreeTimeCard 
  client:load
  cardTitle="Rauchfreie Zeit" 
  iconClass="fa-solid fa-ban-smoking"
>
</SmokeFreeTimeCard>

2. 优化SmokeFreeTimeCard.jsx

import { useState, useEffect } from 'preact/hooks';
import Card from '../components/Card';

const SmokeFreeTimeCard = ({ cardTitle, iconClass }) => {
  const [timeElapsed, setTimeElapsed] = useState(0);
  const [startDate, setStartDate] = useState(null);

  useEffect(() => {
    // 初始化时读取一次localStorage中的起始日期
    const storedDate = localStorage.getItem('dateOfReturn');
    if (storedDate) {
      setStartDate(new Date(storedDate));
    }

    // 只有当起始日期存在时才启动定时器
    const interval = setInterval(() => {
      if (startDate) {
        const currentDate = new Date();
        const elapsedMilliseconds = currentDate - startDate;
        setTimeElapsed(elapsedMilliseconds);
      }
    }, 1000);
  
    return () => clearInterval(interval);
  }, [startDate]); // 依赖startDate,确保日期更新时重新计算

  const formatTime = (milliseconds) => {
    const seconds = Math.floor(milliseconds / 1000);
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const remainingSeconds = seconds % 60;
    return `${hours} Stunden ${minutes} Minuten ${remainingSeconds} Sekunden`;
  };

  return (
    <Card cardTitle={cardTitle} iconClass={iconClass}>
        {startDate ? (
            <>Du bist <b>{formatTime(timeElapsed)}</b> rauchfrei! Weiter so!</>
          ) : (
            <>Bitte setze zuerst deinen Rauchfrei-Startdatum!</>
          )}
    </Card>
  );
};

export default SmokeFreeTimeCard;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 08:31:15