使用React和本地存储的进度跟踪器
进度跟踪器 使用React和本地存储是一个基本的网站,列出用户需要完成的任务。当用户完成任何一个主题时,可以勾选该主题以标记为已完成,进度将显示给用户。为了存储这个进度,以便用户稍后可以回来查找之前的进度,我们使用浏览器中的本地存储来存储进度。我们还有一个固定在顶部的进度条,用于显示个别任务的整体进度。
最终输出的预览:
使用的技术/先决条件
- ReactJs
- Tailwind CSS
- JSX
- React中的功能组件
- React Hooks
- 本地存储
方法: 容器是有状态的React组件(基于类)。组件是无状态的React组件(基于函数)。在这个项目中,我们使用了React功能组件和React Hooks,具体来说是useState和useEffect hooks来构建网站。组件使用JSX编写以呈现UI并编写其中的逻辑。所有这些工作都在基于Javascript的React上进行。
项目结构
步骤
步骤1: 使用以下命令设置React项目
npx create-react-app <name of project>
步骤2: 使用以下方法导航到项目文件夹中:
cd <name_of_project>
步骤3: 创建一个名为“ components ”的文件夹,用于存储组件,创建一个名为“ utils ”的文件夹,我们将在其中创建实用变量和函数来存储每个概念的初始进度并计算进展。
在“components”文件夹中,添加3个文件,分别为“DSAList.js”,“Section.js”,“SubSection.js”,并在“utils”文件夹中创建一个名为“calculateProgress.js”和“dsaTrackerList.js”的文件。
步骤4: 要在您的项目中添加tailwindcss,请在“index.html”中添加以下script标签。
<script src="https://cdn.tailwindcss.com"></script>
将以下代码写入不同的文件中(文件名在每个代码块的第一行提到)
示例:
- index.html :需要导入tailwindcss标签的自动生成的文件。
- index.js :React用于最终渲染的自动生成的文件。
- App.js :此文件导入DSAList组件并将其与导出的标题和应用程序名称一起导出。
- DSAList.js :此文件包含网站的总体逻辑和所有所需的组件。
- Section.js :此组件用于定义单个部分的UI和逻辑。
- SubSection.js :此组件用于定义单个子部分的UI和逻辑。
- calculateProgess.js :此文件用于计算用户在每个主题中的进度以及总体进度。
- dsaTrackerList.js :此文件存储进度对象的初始状态。
// App.js
import DSAList from "./components/DSAList";
export default function App() {
return (
<>
<div className="flex flex-col justify-center items-center mt-4">
<h1 className="text-emerald-500 font-bold text-3xl">
GeeksforGeeks
</h1>
<h3 className="bg-clip-text text-transparent bg-gradient-to-r
from-purple-500 to-pink-500 font-bold text-xl mb-4">
DSA Tracker
</h3>
<DSAList />
</div>
</>
);
}
// DSAList.js
import { useState, useEffect } from "react";
import {
findSectionProgress,
findOverallProgress,
} from "../utils/calculateProgress";
import dsaTrackerList from "../utils/dsaTrackerList";
import Section from "./Section";
export default function DSAList() {
const [dsaList, setDsaList] = useState([]);
const [overallProgress, setOverallProgress] = useState(0);
useEffect(() => {
const localList = JSON.parse(localStorage.getItem("dsalist")) || [];
setDsaList(localList.length !== 0 ? localList : dsaTrackerList);
}, []);
useEffect(() => {
setOverallProgress(findOverallProgress(dsaList));
}, [dsaList]);
const updateList = (index, indexOfSub) => {
const newDSAList = [...dsaList];
newDSAList[index].subsections[indexOfSub].completed =
!newDSAList[index].subsections[indexOfSub].completed;
newDSAList[index].progress = findSectionProgress(
newDSAList[index].subsections
);
setDsaList(newDSAList);
localStorage.setItem("dsalist", JSON.stringify(newDSAList));
};
return (
<div className="flex flex-col gap-10 w-[60%] mb-40 relative">
{overallProgress === 100 && (
<h1 className="text-center text-4xl text-emerald-500">
Successfully Completed! Hurray.
</h1>
)}
<p>Progress: {overallProgress}%</p>
<div
className={`-mt-5 rounded sticky top-0
bg-gradient-to-r from-purple-500
to-pink-500 transition-all h-2 w-[${overallProgress}%]`}>
</div>
{dsaList.map((section, index) => {
return (
<Section
index={index}
updateList={updateList}
key={index}
section={section}
/>
);
})}
</div>
);
}
// Section.js
import { useState } from "react";
import SubSection from "./SubSection";
export default function Section({ section, index, updateList }) {
const [open, setOpen] = useState(false);
return (
<div
className="bg-gray-200 px-10 py-6 w-full
text-slate-800 rounded shadow-lg
transition hover:shadow-2xl
hover:-translate-y-2 hover:scale-[101%]"
>
<div className="flex w-full justify-between items-center cursor-pointer">
<h3
className="font-bold text-xl flex-1"
onClick={() => setOpen((prev) => !prev)}
>
{section.title}
</h3>
<div className="flex gap-4 items-center">
<p className="font-bold text-slate-800">
{section.progress}%
</p>
<button
onClick={() => setOpen((prev) => !prev)}
className="bg-gray-800 text-white px-5 py-3
rounded hover:bg-gray-600"
>
{open ? "Close" : "Open"}
</button>
</div>
</div>
{open && (
<div className="flex flex-col w-full my-10 gap-4">
{section.subsections.map((sub, i) => {
return (
<SubSection
key={i}
index={i}
sectionIndex={index}
updateList={updateList}
subtitle={sub.subtitle}
completed={sub.completed}
/>
);
})}
</div>
)}
</div>
);
}
// SubSection.js
export default function SubSection({
subtitle,
completed,
index,
sectionIndex,
updateList,
}) {
return (
<div className="flex w-full justify-between items-center">
<h4 className="font-bold text-lg">
<span className="inline-block mr-4">{index}.</span> {subtitle}
</h4>
<input
onChange={() => {
updateList(sectionIndex, index);
}}
checked={completed || false}
type="checkbox"
className="border rounded w-4 h-4 accent-emerald-500"
/>
</div>
);
}
// dsaTrackerList.js
const dsaTrackerList = [
{
title: "Arrays",
progress: 0,
subsections: [
{ subtitle: "Introduction to Arrays", completed: false },
{ subtitle: "Array Operations", completed: false },
{ subtitle: "Common Array Problems", completed: false },
],
},
{
title: "Linked Lists",
progress: 0,
subsections: [
{ subtitle: "Singly Linked Lists", completed: false },
{ subtitle: "Doubly Linked Lists", completed: false },
{ subtitle: "Linked List Operations", completed: false },
],
},
{
title: "Stacks",
progress: 0,
subsections: [
{ subtitle: "Introduction to Stacks", completed: false },
{ subtitle: "Stack Operations", completed: false },
{ subtitle: "Applications of Stacks", completed: false },
],
},
{
title: "Queues",
progress: 0,
subsections: [
{ subtitle: "Introduction to Queues", completed: false },
{ subtitle: "Queue Operations", completed: false },
{ subtitle: "Applications of Queues", completed: false },
],
},
{
title: "Trees",
progress: 0,
subsections: [
{ subtitle: "Binary Trees", completed: false },
{ subtitle: "Binary Search Trees", completed: false },
{ subtitle: "Tree Traversal", completed: false },
{ subtitle: "Balanced Trees", completed: false },
],
},
{
title: "Sorting Algorithms",
progress: 0,
subsections: [
{ subtitle: "Bubble Sort", completed: false },
{ subtitle: "Insertion Sort", completed: false },
{ subtitle: "Merge Sort", completed: false },
{ subtitle: "Quick Sort", completed: false },
],
},
{
title: "Graphs",
progress: 0,
subsections: [
{ subtitle: "Graph Representation", completed: false },
{ subtitle: "Graph Traversal", completed: false },
{ subtitle: "Shortest Path Algorithms", completed: false },
{ subtitle: "Minimum Spanning Tree", completed: false },
],
},
];
export default dsaTrackerList;
// calculateProgress.js
export function findSectionProgress(subsections) {
let completed = 0;
for (let i = 0; i < subsections.length; i++) {
if (subsections[i].completed) completed++;
}
return Math.round((completed / subsections.length) * 100);
}
export function findOverallProgress(dsalist) {
let totalProgress = 0;
for (let i = 0; i < dsalist.length; i++) {
totalProgress += dsalist[i].progress;
}
return Math.round((totalProgress / (dsalist.length * 100)) * 100);
}
HTML
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Project | DSA Tracker</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-800 text-gray-300">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
运行应用程序的步骤
步骤1: 在终端中键入以下命令:
npm start
步骤2: 在Web浏览器中打开以下URL地址:
http://localhost:3000/