使用 ReactJS 从零构建一个功能完备的秒表

在现代前端开发中,构建交互式工具是掌握框架核心概念的绝佳方式。今天,我们将通过构建一个功能完备的秒表应用,来深入探讨 ReactJS 的强大功能。通过这个项目,你不仅会学会如何处理时间逻辑,还能掌握组件化设计、状态管理(Hooks)以及副作用处理等关键技能。

我们的目标是创建一个具备以下功能的秒表:开始暂停恢复重置。为了保持代码的整洁和可维护性,我们将采用组件化的架构,将 UI 和逻辑分离。让我们开始吧!

项目初始化与环境搭建

首先,我们需要搭建一个现代化的 React 开发环境。虽然可以使用传统的 create-react-app,但为了保证构建速度和开发体验,我们选用目前业界流行的 Vite 工具。

步骤 1:创建项目

打开你的终端,运行以下命令来生成一个名为 stopwatch 的基础 React 项目:

npm create vite@latest stopwatch --template react

步骤 2:进入项目目录

项目生成后,进入该文件夹:

cd stopwatch

步骤 3:安装依赖

为了启动项目,我们需要安装必要的依赖包。运行以下命令:

npm install

设计组件架构:模块化思维

在编写代码之前,让我们先构思一下组件的结构。一个优秀的 React 应用应该由职责单一的组件组成。我们将创建以下文件结构:

在 INLINECODEcf663d16 目录下创建一个 INLINECODEb31564f8 文件夹。在其中,我们分别创建三个组件文件夹:

  • StopWatch:这是父组件,充当“大脑”的角色。它负责维护时间状态、处理计时逻辑,并将数据分发给子组件。
  • Timer:这是展示组件,专门负责将时间格式化并渲染在屏幕上。
  • ControlButtons:这也是展示组件,包含用户的交互按钮。

项目结构如下:

/
├── src/
│   ├── Components/
│   │   ├── StopWatch/
│   │   │   ├── StopWatch.jsx
│   │   │   └── StopWatch.css
│   │   ├── Timer/
│   │   │   ├── Timer.jsx
│   │   │   └── Timer.css
│   │   └── ControlButtons/
│   │       ├── ControlButtons.jsx
│   │       └── ControlButtons.css
│   ├── App.js
│   ├── App.css
│   ├── index.js
│   └── index.css

搭建应用入口:App.js 与 根组件

在深入核心逻辑之前,我们需要先配置好应用的入口。我们将 App.js 作为容器,它负责引入我们的秒表组件并进行基本的页面布局。

代码示例:App.js

这里我们使用了 Flexbox 布局来确保秒表始终位于屏幕的正中央,背景使用了柔和的浅灰色,模拟真实的移动应用界面。

import ‘./App.css‘;
import StopWatch from ‘./Components/StopWatch/StopWatch.js‘;

function App() {
    return (
        
{/* 我们的核心秒表组件 */}
); } export default App;

代码示例:App.css

.App {
    /* 设置视口宽高,占据整个屏幕 */
    width: 100vw;
    height: 100vh;
    background-color: rgb(238, 238, 238);
    
    /* 使用 Flexbox 居中内容 */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

核心逻辑:实现 StopWatch 父组件

这是整个应用最复杂的部分。INLINECODE81b70cca 组件不仅包含 UI 结构,还包含了所有的业务逻辑。我们需要使用 React 的 INLINECODE6020ce65 来管理状态,并使用 useEffect 来处理副作用——也就是这里的定时器。

#### 状态设计

我们需要定义三个主要的状态变量:

  • time:存储经过的时间(以毫秒为单位)。初始值为 0。
  • isActive:布尔值,表示秒表是否曾经被启动过。用于区分“从未开始”和“暂停中”的状态。
  • INLINECODE0fa11cb7:布尔值,表示当前是否处于暂停状态。当 INLINECODE8ba19549 为 true 且 isPaused 为 false 时,计时器才会走动。

#### 副作用处理

INLINECODEd32d1317 是处理 INLINECODEa0012288 的最佳场所。我们必须确保在组件卸载或依赖项变化时清除定时器,否则会导致内存泄漏或计时器重叠运行。

代码示例:StopWatch.jsx

import React, { useState } from "react";
import "./StopWatch.css";
import Timer from "../Timer/Timer";
import ControlButtons from "../ControlButtons/ControlButtons";

function StopWatch() {
    // 定义时间状态,单位:毫秒
    const [time, setTime] = useState(0);
    // 定义激活状态:判断是否已经开始过
    const [isActive, setIsActive] = useState(false);
    // 定义暂停状态:判断当前是否暂停
    const [isPaused, setIsPaused] = useState(true);

    React.useEffect(() => {
        let interval = null;

        // 只有当秒表被激活且未暂停时,才启动计时器
        if (isActive && isPaused === false) {
            interval = setInterval(() => {
                // 每次增加 10 毫秒
                setTime((time) => time + 10);
            }, 10);
        } else {
            // 如果满足停止条件,清除定时器
            clearInterval(interval);
        }
        
        // 清除函数:组件卸载或依赖项变化前执行
        return () => {
            clearInterval(interval);
        };
    }, [isActive, isPaused]); // 依赖项:这两个状态变化时重新运行 Effect

    const handleStart = () => {
        setIsActive(true);
        setIsPaused(false);
    };

    const handlePauseResume = () => {
        // 切换暂停状态
        setIsPaused(!isPaused);
    };

    const handleReset = () => {
        // 重置所有状态
        setIsActive(false);
        setTime(0);
    };

    return (
        
{/* 传递时间数据给显示组件 */} {/* 传递控制逻辑和状态给按钮组件 */}
); } export default StopWatch;

代码示例:StopWatch.css

为了让它看起来像一个专业的仪表盘,我们给它加上深色背景和固定尺寸。

.stop-watch {
    /* 模拟手机或智能手表的尺寸 */
    height: 85vh;
    width: 23vw;
    
    /* 深色主题配色 */
    background-color: #0d0c1b;
    
    /* 内部布局 */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    
    /* 可选:添加圆角和阴影使其更美观 */
    border-radius: 20px;
    box-shadow: 0 10px 25px rgba(0,0,0,0.5);
}

数据展示:格式化时间

原始的 INLINECODE08a92967 只是一个数字(比如 12345,代表 12345 毫秒)。我们需要将其转换为易读的 INLINECODEf5045085 格式。这里涉及到一些数学运算:取模和整除。

代码示例:Timer.jsx

import React from "react";
import "./Timer.css";

export default function Timer(props) {
    return (
        
{/* 分钟: / 60000 取模 60 */} {("0" + Math.floor((props.time / 60000) % 60)).slice(-2)}: {/* 秒: / 1000 取模 60 */} {("0" + Math.floor((props.time / 1000) % 60)).slice(-2)}. {/* 毫秒(显示两位): / 10 取模 100 */} {("0" + ((props.time / 10) % 100)).slice(-2)}
); }

#### 代码深度解析:时间格式化技巧

你可能会好奇 INLINECODEdd2e0f3b 是什么意思?这是一个非常实用的技巧。INLINECODE7d61121b 可能会返回 INLINECODEaa557149 或 INLINECODE7af46146 这样的个位数。为了保持格式统一(如 INLINECODE947eade7 或 INLINECODEe6c9744e),我们前面拼接了 INLINECODEa96f5657。如果结果是 INLINECODE81936d5b,INLINECODE009f716d 就会截取最后两位,变成 INLINECODE0428299c。如果结果是 INLINECODE90f02474,它就会截取 INLINECODE6ea888a6。这比写繁琐的 if 语句要优雅得多。

代码示例:Timer.css

.timer {
    margin: 3rem 0;
    width: 100%;
    display: flex;
    height: 12%;
    justify-content: center;
    align-items: center;
    color: #fff;
    font-size: 5rem;
    font-weight: bold;
    font-family: ‘Courier New‘, Courier, monospace; /* 使用等宽字体防止数字跳动 */
}

.digits {
    padding: 0 0.2rem;
}

.mili-sec {
    font-size: 3rem; /* 毫秒数字小一点,增加视觉层次感 */
    padding-top: 1.8rem;
    color: #c2c2c2;
}

交互控制:ControlButtons 组件

最后,我们需要让用户能够控制时间。这个组件根据父组件传来的 INLINECODE8aa2e821 和 INLINECODE651dc880 状态,动态决定显示哪个按钮。我们将使用条件渲染来实现这一点。

代码示例:ControlButtons.jsx

import React from "react";
import "./ControlButtons.css";

export default function ControlButtons(props) {
    return (
        
{/* 如果秒表未激活(从未开始),显示“开始”按钮 */} {(!props.active) ? ( ) : ( // 如果正在运行,显示“暂停/恢复”和“重置”按钮组
)}
); }

代码示例:ControlButtons.css

为了让按钮看起来现代且易于点击,我们添加一些样式。

.Control-btns {
    display: flex;
    gap: 20px;
    margin-bottom: 3rem;
}

.btn-grp {
    display: flex;
    gap: 15px;
}

/* 通用按钮样式 */
button {
    cursor: pointer;
    font-size: 1.2rem;
    border: none;
    padding: 10px 20px;
    border-radius: 5px;
    font-weight: bold;
    transition: all 0.2s ease;
}

.btn-start {
    background-color: #2ecc71;
    color: white;
    padding: 15px 40px; /* 开始按钮大一点 */
}

.btn-pause {
    background-color: #f1c40f;
    color: #333;
}

.btn-resume {
    background-color: #3498db;
    color: white;
}

.btn-reset {
    background-color: #e74c3c;
    color: white;
}

button:hover {
    opacity: 0.9;
    transform: scale(1.05);
}

总结与进阶思考

恭喜你!通过跟随这些步骤,你已经成功构建了一个功能齐全的 React 秒表。在这个过程中,我们实践了以下核心概念:

  • 组件拆分:将复杂的 UI 拆分为 INLINECODEa1805b83、INLINECODE05f0e0f1 和 ControlButtons,遵循了单一职责原则。
  • State Hook:我们使用了 useState 来驱动 UI 的变化,而不是直接操作 DOM。
  • Effect Hook:这是处理定时器、网络请求等副作用的利器。请记住,永远要在 useEffect 的返回函数中清理定时器,这是避免 React 内存泄漏的关键。

#### 潜在的优化方向

如果你想进一步提升这个项目,可以考虑以下挑战:

  • 添加“计次”功能:允许用户记录特定的时间点,而不仅仅是一个总时长。这需要引入一个新的状态来存储计次数组,并映射渲染列表。
  • 本地存储:使用 localStorage 保存用户的最后时间,这样即使刷新页面,数据也不会丢失。
  • 自定义钩子:我们可以将 INLINECODE681c15f2 的逻辑提取到一个自定义 Hook INLINECODE363c9f8d 中,这样代码会更加整洁,逻辑也更加复用。

希望这篇教程能帮助你更好地理解 React。继续编码,继续探索!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/32653.html
点赞
0.00 平均评分 (0% 分数) - 0