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;
问题截图:
修复方案
核心问题
Astro默认对Preact组件采用静态渲染,组件的交互逻辑(比如useEffect里的定时器)不会在客户端浏览器中执行,导致timeElapsed始终保持初始值0。
具体修复步骤
- 激活客户端组件:在调用
SmokeFreeTimeCard时添加client:load指令,强制Astro在客户端加载并激活组件的交互功能。 - 优化定时器逻辑:避免每秒重复读取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
相关产品推荐
相关产品推荐

