实践 React 应用百题挑战之 〜 04 计时器 〜
首先
这篇文章是为了参与@Sicut_study所发布的【React应用100题】系列,并进行输出而撰写的。
-
- 実装ルールや成果物の達成条件は元記事に従うものとします。
- 元記事との差別点として、具体的に自分がどんな実装を行ったのか(と必要に応じて解説)を記載します。
跟随Sicut_study先生进行100个挑战,目标是学习React 100天。
这篇原文可以在这里找到。
上一篇文章
请问这个问题。
创建一个计时器应用
– 规则
从原始文章中引用。
-
- 主要なライブラリやフレームワークはReactである必要がありますが、その他のツールやライブラリ(例: Redux, Next.js, Styled Componentsなど)を組み合わせて使用することは自由
-
- TypeScriptを利用する
- 要件をみたせばデザインなどは自由
实现要求
从原始文章中引用
-
- 用户可以输入分钟和秒钟来设置定时器。
-
- 按下开始按钮后,倒计时将开始。
-
- 当时间到达0时,用户将收到通知(播放音效)。
-
- 按下暂停按钮将暂停倒计时,再次按下恢复按钮将重新开始倒计时。
-
- 按下重置按钮将定时器重设为设定的时间。
- 如果输入了无效的时间(例如:负数时间、非数字、超过60分钟的值),将显示错误消息。
实施
本文将按照以下策略进行实施。
-
- ボタンとテキストボックスをコンポーネント化する
use-soundライブラリを利用して効果音を鳴らす
由于每次只需要输入相同的命令,所以从这次开始,我们将省略项目的创建步骤。
此外,我打算在Emotion中使用css而不是styled(基于个人喜好)。
以下是每个组件和App.tsx的实现。请将喜欢的mp3文件放置在适当位置(如果导入mp3时出现错误,请注意)。
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
interface ButtonProps {
onClick: () => void;
disabled: boolean;
text: string;
}
const buttonStyle = (disabled: boolean) => css`
margin: 10px;
padding: 5px 10px;
background-color: ${disabled ? "#ccc" : "#007bff"};
color: ${disabled ? "#666" : "white"};
border: none;
border-radius: 4px;
cursor: ${disabled ? "not-allowed" : "pointer"};
&:hover {
background-color: ${disabled ? "#ccc" : "#0056b3"};
}
`;
const Button = ({ onClick, disabled, text }: ButtonProps) => {
return (
<button css={buttonStyle(disabled)} onClick={onClick} disabled={disabled}>
{text}
</button>
);
};
export default Button;
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
interface InputFieldProps {
label: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
const inputFieldStyle = css`
margin: 10px;
label {
font-weight: bold;
}
input {
margin-left: 5px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 4px;
}
`;
const InputField = ({ label, value, onChange }: InputFieldProps) => {
return (
<div css={inputFieldStyle}>
<label>
{label}
<input type="number" value={value} onChange={onChange} />
</label>
</div>
);
};
export default InputField;
/** @jsxImportSource @emotion/react */
import { useState, useEffect } from "react";
import { css } from "@emotion/react";
import InputField from "./components/InputField";
import Button from "./components/Button";
import useSound from "use-sound";
import beepSound from "./sounds/beep.mp3";
const appStyle = css`
text-align: center;
margin-top: 50px;
`;
const errorStyle = css`
color: red;
margin-top: 20px;
`;
const App = () => {
const [minutes, setMinutes] = useState<string>("0");
const [seconds, setSeconds] = useState<string>("0");
const [totalSeconds, setTotalSeconds] = useState<number>(0);
const [isActive, setIsActive] = useState<boolean>(false);
const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
// useSoundフックでサウンドをセットアップ
const [play] = useSound(beepSound, { volume: 0.5 });
useEffect(() => {
let id: NodeJS.Timeout | null = null;
if (isActive && totalSeconds > 0) {
id = setInterval(() => {
setTotalSeconds((seconds) => seconds - 1);
}, 1000);
setIntervalId(id);
} else if (totalSeconds === 0 && isActive) {
clearInterval(intervalId as NodeJS.Timeout);
play(); // 効果音を再生する
setIsActive(false);
}
return () => {
if (id) clearInterval(id);
};
}, [isActive, totalSeconds]);
const toggleActive = () => {
setIsActive(!isActive);
};
const handleStart = () => {
const totalSec = parseInt(minutes) * 60 + parseInt(seconds);
if (!isNaN(totalSec) && totalSec >= 0) {
setTotalSeconds(totalSec);
setIsActive(true);
setErrorMessage(null);
} else {
setErrorMessage("Invalid time input!");
}
};
const handleReset = () => {
setIsActive(false);
setTotalSeconds(parseInt(minutes) * 60 + parseInt(seconds));
};
const handleMinutesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setMinutes(e.target.value);
};
const handleSecondsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSeconds(e.target.value);
};
return (
<div css={appStyle}>
<h1>Timer App</h1>
{errorMessage && <div css={errorStyle}>{errorMessage}</div>}
<InputField
label="Minutes:"
value={minutes}
onChange={handleMinutesChange}
/>
<InputField
label="Seconds:"
value={seconds}
onChange={handleSecondsChange}
/>
<Button onClick={handleStart} disabled={isActive} text="Start" />
<Button
onClick={toggleActive}
disabled={!isActive && totalSeconds === 0}
text={isActive ? "Pause" : "Resume"}
/>
<Button
onClick={handleReset}
disabled={!isActive && totalSeconds === 0}
text="Reset"
/>
<h2>
Time Remaining: {Math.floor(totalSeconds / 60)}:
{totalSeconds % 60 < 10 ? `0${totalSeconds % 60}` : totalSeconds % 60}
</h2>
</div>
);
};
export default App;
提供完善
导入mp3时,可能会出现以下错误。
Cannot find module './sounds/beep.mp3' or its corresponding type declarations.
当出现这样的错误时,您可以创建src/types/custom.d.ts文件,并编写以下代码来解决问题。
declare module "*.mp3" {
const src: string;
export default src;
}
做完
完成的形式是这样的。
最后
我终于赶上了原版的React应用程序100个系列的快速帖子的节奏。我将继续努力并力争完赛100次。
如果你支持我,希望能够关注我,我会很高兴。
喜欢和收藏也期待着。
再见。
下一篇文章