湖北建设信息网站,做时间轴的在线网站,手机做兼职的网站设计,做电子元器件销售什么网站好如果你是React.js的新手#xff0c;并且渴望投身应用程序开发#xff0c;那么你来对地方了#xff01;
跟着我一起通过这个教程#xff0c;从头开始构建一个基本的TODO应用程序。
#xff08;本文视频讲解#xff1a;java567.com#xff09;
TODO应用对初学者的重要性…如果你是React.js的新手并且渴望投身应用程序开发那么你来对地方了
跟着我一起通过这个教程从头开始构建一个基本的TODO应用程序。
本文视频讲解java567.com
TODO应用对初学者的重要性
TODO应用作为初学者掌握新编程语言或框架基础知识的理想项目。它为学习基本概念提供了实际的上下文并朝着可实现的结果努力。
如果你正在开始React.js之旅跟着这个教程一起构建TODO应用可能是一个完美的起点。
先决条件
在我们开始之前请确保你具备React.js的基本知识并在计算机上安装了Node.js和npm。如果还没有请花点时间设置你的开发环境。
我们的目标
我们的目标是创建一个简单的TODO应用具备以下功能。我们将朝着以下目标努力
添加新的TODO使用户能够将新任务添加到列表中。编辑和删除TODO提供修改或删除现有任务的功能。将TODO标记为已完成允许用户指示任务何时完成。跟踪已完成的TODO实现一个功能来跟踪所有已完成的任务。
如果你愿意可以随意扩展这个列表添加额外的功能。对于这个教程我们将专注于这些核心功能。
这是我们将要构建的TODO应用的示例
我们的todo应用预览
目录 如何设置你的 React 应用程序 如何构建组件 头部组件TODOHero 组件表单组件TODOList 组件 将所有内容组合在一起 样式 构建功能如何添加待办事项 存储待办事项数据的方式我们想要存储的数据类型是什么如何将待办事项数据传递给我们的组件向我们的状态添加更多待办事项数据 如何构建 TODO 应用程序的功能 如何标记待办事项为已完成如何编辑待办事项如何删除待办事项 如何持久化我们的待办事项数据 如何将待办事项数据持久化到 localStorage如何从 localStorage 中读取待办事项数据 我们完成了。
如何设置你的React应用
在2024年使用Next.js或Remix等框架是启动React项目的推荐方法。这两个框架都可以胜任所以只需选择你最熟悉的一个。在本教程中我们将使用Next.js。
要使用Next.js创建一个React应用请转到你喜欢的目录并运行以下命令
npx create-next-applatest注意我们不会在这个项目中使用TypeScript和TailwindCSS所以你可以使用默认设置继续。
安装完成后进入你新创建的应用程序目录我命名为’todo’并启动开发服务器
cd todo
npm run dev
## 或者使用yarn
cd todo
yarn run dev当你的开发服务器启动并运行时我们就可以开始构建我们的TODO应用了
如何构建组件
在React中我们使用组件来构建用户界面。我们的TODO应用的UI由几个部分组成。让我们一一解析
头部组件
头部组件用于显示我们应用的标题。我们不会直接嵌入HTML而是在一个React组件中构建这个功能。
首先创建一个组件目录
# 在你的项目根目录下创建一个新目录
mkdir src/components
# 进入目录
cd src/components
# 为头部组件创建一个新文件
touch Header.jsx在React中组件本质上是返回HTML的JavaScript函数。在我们的Header.jsx文件中定义一个返回我们头部组件的HTML内容的函数
// src/components/Header.jsx
function Header() {return (svgpath d / /svgh1TODO/h1/);
}export default Header;我们导出Header函数以便我们可以在整个项目中使用它。
TODOHero组件
TODO Hero组件在我们的应用程序中起着关键作用。它作为一个部分我们在其中提供总的todo数和已完成任务数的概览。
与头部组件不同头部组件在我们应用的使用过程中保持静态不变TODOHero组件是动态的。它会根据已完成的todo数和总的todo数不断更新。
在构建组件时早期识别动态部分很重要。在React中我们通过向组件传递参数称为props来实现这一点。
让我们创建TODOHero组件。首先确保你在src/components目录中
cd src/components现在为TODOHero组件创建一个新文件
touch TODOHero.jsx在TODOHero.jsx中定义一个接受props作为参数的函数
// src/components/TODOHero.jsx
function TODOHero({ todos_completed, total_todos }) {return (sectiondivpTask Done/ppKeep it up/p/divdiv{todos_completed}/{total_todos}/div/section);
}
export default TODOHero;这个函数返回我们的TODOHero组件的HTML内容。我们使用props来动态更新已完成的todo数和总的todo数。
表单组件
我们的表单组件将是一个简单的输入和一个提交按钮所以继续创建一个新组件
touch src/components/Form.jsx就像我说的这将是一个非常简单的表单只有一个带有提交按钮的输入。标签是为了可访问性。
// src/components/Form.jsxfunction Form() {const handleSubmit (event) {event.preventDefault();// 重置表单event.target.reset();};return (form classNameform onSubmit{handleSubmit}label htmlFortodoinputtypetextnametodoidtodoplaceholderWrite your next task//labelbuttonspan classNamevisually-hiddenSubmit/spansvgpath d //svg/button/form);
}
export default Form;我们在表单上添加了一个onSubmit事件使用了一个handleSubmit事件处理程序。event.preventDefault()阻止表单提交并重新加载整个应用程序。最后我们使用event.target.reset()重置表单。
TODOList组件
最后让我们创建列表组件。首先创建一个名为TODOList.jsx的新组件文件
touch src/components/TODOList.jsx列表本身是一个简单的有序列表
// src/components/TODOList.jsxfunction TODOList() {return ol classNametodo_list{/* li list goes here */}/ol;
}
export default TODOList;列表项将从todo数据动态生成。但在我们继续之前让我们为列表项创建一个单独的组件。
在React中几乎所有东西都是一个组件所以我们将在TODOList组件旁边创建Item组件
// src/components/TODOList.jsxfunction Item({ item }) {return (li id{item?.id} classNametodo_itembutton classNametodo_items_leftsvgcircle cx11.998 cy11.998 fillRulenonzero r9.998 //svgp{item?.title}/p/buttondiv classNametodo_items_rightbuttonspan classNamevisually-hiddenEdit/spansvgpath d //svg/buttonbuttonspan classNamevisually-hiddenDelete/spansvgpath d //svg/button/div/li);
}列表项本身只是一个具有编辑和删除任务按钮的li元素。我们确保了li本身不可点击遵循“网页上可点击的任何内容应该是按钮或链接”的原则。
现在我们可以在我们的列表中使用Item组件
// src/components/TODOList.jsxfunction TODOList({ todos }) {return (ol classNametodo_list{todos todos.length 0 ? (todos?.map((item, index) Item key{index} item{item} /)) : (p这里看起来有点寂寞你在干嘛/p)}/ol);
}
export default TODOList;有了这些组件我们的TODO应用程序的UI就完全构建好了。
将所有内容整合起来
到目前为止我们已经创建了四个独立的组件每个组件本身并没有做太多事情。现在我们需要在我们的首页中渲染这些组件。
在Next.js中页面位于src/app目录中索引页面通常命名为page.js。
首先让我们清空文件的内容因为我们不需要其中的任何内容
echo -n src/app/page.js接下来导入我们创建的所有组件并在page.js文件中利用它们如下所示
// src/app/page.jsimport React from react;
import Form from /components/Form;
import Header from /components/Header;
import TODOHero from /components/TODOHero;
import TODOList from /components/TODOList;
function Home() {return (div classNamewrapperHeader /TODOHero todos_completed{0} total_todos{0} /Form /TODOList todos{[]} //div);
}
export default Home;通过在浏览器中查看输出应该会看到类似于以下内容的内容
我们的应用程序的预览没有CSS
样式
对于样式我们将坚持使用老式的CSS。让我们创建一个styles.css文件来保存我们的样式
touch src/app/styles.css此外删除安装Next.js时附带的所有CSS文件因为我们不需要它们
rm src/app/page.module.css src/app/globals.css现在你可以在styles.css文件中添加你的CSS规则。虽然不完美但以下CSS应该足够我们简单的示例
*,
*::after,
*::before {padding: 0;margin: 0;font-family: inherit;box-sizing: border-box;
}
html,
body {font-family: sans-serif;background-color: #0d0d0d;color: #fff;display: flex;align-items: center;justify-content: center;width: 100vw;
}
button {cursor: pointer;
}
.visually-hidden {position: absolute !important;clip: rect(1px, 1px, 1px, 1px);padding: 0 !important;border: 0 !important;height: 1px !important;width: 1px !important;overflow: hidden;white-space: nowrap;
}
.text_large {font-size: 32px;
}
.text_small {font-size: 24px;
}
.wrapper {display: flex;flex-direction: column;width: 70%;
}
media (max-width: 510px) {.wrapper {width: 100%;}header {justify-content: center;}
}
header {display: flex;align-items: center;justify-content: flex-start;gap: 12px;padding: 42px;
}
.todohero_section {border: 1px solid #c2b39a;display: flex;align-items: center;justify-content: space-around;align-self: center;width: 90%;max-width: 455px;padding: 12px;border-radius: 11px;
}
.todohero_section div:last-child {background-color: #88ab33;width: 150px;height: 150px;border-radius: 75px;font-size: 48px;display: flex;align-items: center;justify-content: center;text-align: center;
}
.form {align-self: center;width: 97%;max-width: 455px;display: flex;align-items: center;gap: 12px;margin-top: 38px;
}
.form label {width: 90%;
}
.form input {background-color: #1f2937;color: #fff;width: 100%;height: 50px;outline: none;border: none;border-radius: 11px;padding: 12px;
}
.form button {width: 10%;height: 50px;border-radius: 11px;background-color: #88ab33;border: none;
}
.todo_list {align-self: center;width: 97%;max-width: 455px;display: flex;flex-direction: column;align-items: center;margin-top: 27px;margin-bottom: 27px;gap: 27px;
}
.todo_item,
.edit-form input {display: flex;justify-content: space-between;align-items: center;height: 70px;width: 100%;max-width: 455px;border: 1px solid #c2b39a;font-size: 16px;background-color: #0d0d0d;color: #fff;padding: 12px;
}
.edit-form input {outline: transparent;width: calc(100% - 14px);height: calc(100% - 12px);border: transparent;
}
.todo_items_left,
.todo_items_right {display: flex;align-items: center;
}
.todo_items_left {background-color: transparent;border: none;color: #fff;gap: 12px;font-size: 16px;
}
.todo_items_right {gap: 4px;
}
.todo_items_right button {background-color: transparent;color: #fff;border: none;
}
.todo_items_right button svg {fill: #c2b39a;
}最后我们需要在我们的布局中导入CSS文件。打开位于page.js旁边的layout.js文件并像下面示例中所示导入CSS文件
一个显示如何在我们的组件中导入styles.css文件的图像
再次预览应用程序时它现在应该反映出应用的样式
添加CSS后应用程序的预览
构建功能如何添加待办事项
在这个阶段我们已经创建了一个外观上吸引人的待办事项应用程序但它缺少功能。在本节中让我们改变这一点。
存储待办事项数据的方法
首先我们需要一种方法来存储我们的待办事项数据。在React中可以使用状态来实现这一点——状态是一个关于组件状态的信息的JavaScript对象。
React提供了一个名为 useState() 的钩子它使我们能够在React应用程序中管理状态。但在Next.js中在使用 useState 之前你需要指定该组件是一个客户端组件。
将以下代码添加到你的src/app/page.js文件的顶部
use client;如下图所示
一个显示如何将use client添加到我们的page.js文件顶部的图像
现在我们可以使用 useState 钩子来为我们的待办事项数据创建一个状态
// src/app/page.jsuse client;
import React from react;
import Form from /components/Form;
// 添加其他组件的导入
function Home() {const [todos, setTodos] React.useState([]);return (Header /// 在这里添加其他组件);
}
export default Home;在上面的代码片段中你会注意到 useState 最初保存一个空数组。重要的是要理解useState 返回两个值todos 和 setTodos你可以用任何你喜欢的名称命名它们。
第一个值 todos 保存状态的当前值而 setTodos第二个值是用于更新状态的函数。到目前为止清楚吗
我们想要存储什么样的数据
现在我们有了存储数据的方法让我们定义我们打算存储的数据类型。基本上它将是一个对象数组其中每个对象保存了渲染我们的待办事项列表所需的信息
const [todos, setTodos] React.useState([
{ /* 对象 */ },
{ /* 对象 */ },
{ /* 对象 */ },
]);数组中的每个对象将具有以下结构
{
title: 一些任务, // 字符串
id: self.crypto.randomUUID(), // 字符串
is_completed: false // 布尔值
}在这里self.crypto.randomUUID() 是一个允许浏览器为每个待办事项生成唯一ID的方法。如果你查看控制台你会观察到生成的ID确实是唯一的。
我们的待办事项数据的console.log
这个结构确保了每个待办事项都有一个标题、一个唯一标识符id和一个布尔值表示任务是否完成is_completed。
如何将待办事项数据传递给我们的组件
在React中有一个称为状态共享的概念它允许子组件访问其父组件的状态。这意味着我们之前创建的待办事项状态可以在所有我们的组件之间共享。
我们需要数据的第一个地方是在我们的List组件中。让我们将状态传递给List组件
// src/app/page.jsuse client;
import React from react;
// 导入其他组件
import TODOList from /components/TODOList;function Home() {const [todos, setTodos] React.useState([{ title: 一些任务, id: self.crypto.randomUUID(), is_completed: false },{title: 另一个任务,id: self.crypto.randomUUID(),is_completed: true,},{ title: 最后一个任务, id: self.crypto.randomUUID(), is_completed: false },]);return (div classNamewrapper...TODOList todos{todos} //div);
}
export default Home;我们已经在List组件中预留了接收 todos 属性的位置
// src/components/TODOList.jsxfunction TODOList({ todos }) {return (ol classNametodo_list{todos todos.length 0 ? (todos?.map((item, index) (Item key{index} item{item} setTodos{setTodos} /))) : (p这里看起来有点孤单你在干什么/p)}/ol);
}现在todos属性将由我们状态中的数据填充并且不需要任何额外的操作它将起作用。以下是一个显示了根据我们的todos数据创建的列表的图像
我们需要数据的另一个地方是我们的 TODOHero 组件。在该组件中我们不需要所有的数据——我们只需要计算总待办事项数量和已完成的待办事项数量
// src/app/page.jsuse client;
import React from react;
// 导入其他组件
import TODOHero from /components/TODOHero;
import TODOList from /components/TODOList;
function Home() {const [todos, setTodos] React.useState([{ title: 一些任务, id: self.crypto.randomUUID(), is_completed: false },// 添加其他虚拟数据]);const todos_completed todos.filter((todo) todo.is_completed true).length;const total_todos todos.length;return (div classNamewrapperHeader /TODOHero todos_completed{todos_completed} total_todos{total_todos} /Form /TODOList todos{todos} //div);
}
export default Home;在这里使用JavaScript的filter方法来过滤所有 is_completed 设置为true的待办事项然后我们得到长度。total_todos 简单地是整个数组的长度。
以下是一个显示了更新值的 TODOHero 组件的图像
显示了更新后的TODOHero组件
向我们的状态添加更多待办事项数据
目前我们的待办事项应用程序显示了来自我们虚拟数据的待办事项
const [todos, setTodos] React.useState([{ title: Some task, id: self.crypto.randomUUID(), is_completed: false },{title: Some other task,id: self.crypto.randomUUID(),is_completed: true,},{ title: last task, id: self.crypto.randomUUID(), is_completed: false },
]);但创建一个 Form 组件的目的是为了让我们自己创建新的待办事项而不是依赖于虚拟数据。
好消息是正如我们可以访问待办事项状态数据一样我们也可以从子组件更新父组件的状态。这意味着我们可以将用于更新状态的函数 setTodos 传递给我们的 Form 组件
// src/app/page.jsuse client;
import React from react;
import Form from /components/Form;
// 导入其他组件function Home() {const [todos, setTodos] React.useState([{ title: Some task, id: self.crypto.randomUUID(), is_completed: false },// 添加其他虚拟数据]);...return (div classNamewrapper...Form setTodos{setTodos} /TODOList todos{todos} //div);
}
export default Home;有了在我们的 Form 组件中访问 setTodos 函数的权限我们现在可以在提交表单时向我们的状态添加新的待办事项
// src/components/Form.jsxfunction Form({ setTodos }) {const handleSubmit (event) {event.preventDefault();const value event.target.todo.value;setTodos((prevTodos) [...prevTodos,{ title: value, id: self.crypto.randomUUID(), is_completed: false },]);event.target.reset();};return (form classNameform onSubmit{handleSubmit}…/form);
}
export default Form;下面的代码片段是魔法发生的地方
setTodos((prevTodos) [...prevTodos,{ title: value, id: self.crypto.randomUUID(), is_completed: false },
]);这相当于在普通JavaScript中执行以下操作
let prevTodos [];prevTodos.push({title: value,id: self.crypto.randomUUID(),is_completed: false,
});现在我们可以自己向我们的状态添加新的待办事项我们可以摆脱虚拟数据了。我们不再需要它。让我们回到使用一个空数组
const [todos, setTodos] React.useState([]);如何构建 TODO 应用程序的功能
如何标记待办事项为完成
在我们的 List 组件中我们构建了一个带有按钮的 li 元素。现在我们要为第一个按钮附加一个 onClick 事件处理程序。
// src/components/TODOList.jsxfunction Item({ item }) {const completeTodo () {// 执行一些操作};return (li id{item?.id} classNametodo_item onClick{completeTodo}button classNametodo_items_leftsvgcircle cx11.998 cy11.998 fillRulenonzero r9.998 //svgp{item?.title}/p/buttondiv classNametodo_items_rightbutton.../buttonbutton.../button/div/li);
}当我们点击此按钮并调用 completeTodo 处理程序时我们的目标是
过滤数据以找到触发删除事件的待办事项。修改数据并将 is_completed 值设置为 true。
在我们继续进行数据修改之前我们需要在我们的 Item / 组件中访问 setTodo 函数。幸运的是React 允许将状态传递给孙组件。
这意味着我们可以从 List / 组件将 setTodo 函数传递给我们的 Item / 组件
// src/app/page.jsuse client;
import React from react;
// 导入其他组件
import TODOList from /components/TODOList;function Home() {const [todos, setTodos] React.useState([]);...return (div classNamewrapper...TODOList todos{todos} setTodos{setTodos} //div);
}
export default Home;然后在我们的 List / 组件中我们将 setTodo 函数传递给我们的 Item / 组件
// src/components/TODOList.jsxfunction TODOList({ todos, setTodos }) {return (ol classNametodo_list{todos todos.length 0 ? (todos?.map((item, index) (Item key{index} item{item} setTodos{setTodos} /))) : (p这里看起来有点孤单你在干什么/p)}/ol);
}现在在我们的 Item / 组件中当按钮被点击时我们可以使用 setTodos 函数来更新待办事项的 is_completed 状态
// src/components/TODOList.jsxfunction Item({ item, setTodos }) {const completeTodo () {setTodos((prevTodos) prevTodos.map((todo) todo.id item.id? { ...todo, is_completed: !todo.is_completed }: todo));};return (li id{item?.id} classNametodo_itembutton classNametodo_items_left onClick{completeTodo}.../buttondiv classNametodo_items_rightbutton.../buttonbutton.../button/div/li);
}现在点击待办事项中的第一个按钮将切换其完成状态从而有效地修改了待办事项数据。
当待办事项标记为已完成时我们希望增强其视觉表示。这包括向待办事项标题旁边的 SVG 圆圈添加填充以创建待办事项已完成的幻觉。此外我们希望为文本添加删除线以表示已完成。
button classNametodo_items_left onClick{completeTodo}svg fill{item.is_completed ? #22C55E : #0d0d0d}circle cx11.998 cy11.998 fillRulenonzero r9.998 //svgp style{item.is_completed ? { textDecoration: line-through } : {}}{item?.title}/p
/button;在上面的代码片段中按钮的颜色根据待办事项的完成状态更改。如果项目已完成is_completed 为 trueSVG 圆圈将填充为绿色 - 否则它将填充为深色。此外如果待办事项已完成待办事项标题文本将接收删除线样式以在视觉上表示其已完成状态。
如何编辑待办事项
在编辑待办事项时我们希望有一个表单可以在其中编辑待办事项的标题。当单击编辑按钮时我们希望将 li 中的所有内容替换为表单
// src/components/TODOList.jsxfunction Item({ item, setTodos }) {const [editing, setEditing] React.useState(false);const inputRef React.useRef(null);const completeTodo () {// 标记待办事项为完成};const handleEdit () {setEditing(true);};React.useEffect(() {if (editing inputRef.current) {inputRef.current.focus();// 将光标定位到文本的末尾inputRef.current.setSelectionRange(inputRef.current.value.length,inputRef.current.value.length);}}, [editing]);const handleInpuSubmit (event) {event.preventDefault();setEditing(false);};const handleInputBlur () {setEditing(false);};return (li id{item?.id} classNametodo_item{editing ? (form classNameedit-form onSubmit{handleInpuSubmit}label htmlForedit-todoinputref{inputRef}typetextnameedit-todoidedit-tododefaultValue{item?.title}onBlur{handleInputBlur}onChange{handleInputChange}//label/form) : (button classNametodo_items_left onClick{completeTodo}.../buttondiv classNametodo_items_rightbutton onClick{handleEdit}.../buttonbutton.../button/div/)}/li);
}我知道上面的代码有点多。好吧这是因为我们在这里做了很多事情 - 但我们首先创建了一个状态
const [editing, setEditing] React.useState(false);当单击编辑按钮时我们将编辑状态的值设置为 true这将渲染我们的表单
const handleEdit () {setEditing(true);
};现在当我们通过按回车键提交编辑待办事项表单时我们还希望将变量设置回 false以便我们可以重新获取我们的列表
const handleInpuSubmit (event) {event.preventDefault();setEditing(false);
};当我们鼠标移出编辑表单时我们也希望将状态设置回 false
const handleInputBlur () {setEditing(false);
};我们想要做的另一件事是一旦编辑设置为 true就将焦点集中到输入框上
React.useEffect(() {if (editing inputRef.current) {inputRef.current.focus();// 将光标定位到文本的末尾inputRef.current.setSelectionRange(inputRef.current.value.length,inputRef.current.value.length);}
}, [editing]);编辑待办事项本身只有一个输入字段带有一个 onChange 事件。当我们在输入字段中编辑标题时我们希望使用更新的标题修改当前待办事项
const handleInputChange (e) {setTodos((prevTodos) prevTodos.map((todo) todo.id item.id ? { ...todo, title: e.target.value } : todo));
};JavaScript 的 array.map() 方法非常适合此操作因为它返回一个具有相同数量的元素的新数组在修改标题后。
如何删除待办事项
删除待办事项是一个简单的过程。当单击删除按钮时我们会从待办事项列表中过滤掉触发删除事件的待办事项。
// src/components/TODOList.jsxconst handleDelete () {setTodos((prevTodos) prevTodos.filter((todo) todo.id ! item.id));
};不要忘记为删除按钮添加一个 onClick 事件
// src/components/TODOList.jsxfunction Item({ item, setTodos }) {...const handleDelete () {setTodos((prevTodos) prevTodos.filter((todo) todo.id ! item.id));};return (li id{item?.id} classNametodo_item{editing ? (form classNameedit-form onSubmit{handleInpuSubmit}.../form) : (…div classNametodo_items_right…button onClick{handleDelete}span classNamevisually-hidden删除/spansvgpath d //svg/button/div/)}/li);
}如何持久化我们的待办事项数据
到目前为止我们的待办事项数据仅存储在应用程序的状态中
const [todos, setTodos] React.useState([]);虽然这种方法有效但它也带来了一个挑战当应用程序重新加载时所有待办事项数据都会丢失。
当涉及到持久化数据时我们通常会考虑数据库。将我们的待办事项数据存储在数据库中具有几个优点比如易于从任何设备访问。但还有一种替代方案localStorage。
LocalStorage 是一个基于浏览器的存储系统。它有一些限制比如 5MB 的存储限制以及数据只能在存储它的浏览器中访问。尽管存在这些缺点但出于简单起见我们将在本教程中使用 localStorage。
如何将待办事项数据持久化到 localStorage
当前当我们添加新的待办事项时我们仅在我们的 Form 组件中更新待办事项状态
// src/components/Form.jsxconst handleSubmit (event) {event.preventDefault();const value event.target.todo.value;setTodos((prevTodos) [...prevTodos,{ title: value, id: self.crypto.randomUUID(), is_completed: false },]);event.target.reset();
};我们仍然希望保留这一点但同时我们也希望将相同的数据添加到 localStorage 中因此我们将修改上面的代码如下所示
// src/components/Form.jsx const handleSubmit (event) {event.preventDefault();const value event.target.todo.value;const newTodo {title: value,id: self.crypto.randomUUID(),is_completed: false,};// 更新待办事项状态setTodos((prevTodos) [...prevTodos, newTodo]);// 将更新后的待办事项列表存储到本地存储中const updatedTodoList JSON.stringify([...todos, newTodo]);localStorage.setItem(todos, updatedTodoList);event.target.reset();
};我是否提到过你只能在 localStorage 中存储字符串我们不能在 localStorage 中存储数组或对象。这就是为什么我们首先将我们的待办事项数据数组转换为字符串的原因
const updatedTodoList JSON.stringify([...prevTodos, newTodo]);然后最后我们用这段代码持久化数据在 localStorage 中
localStorage.setItem(todos, updatedTodoList);你会注意到我们在我们的 Form / 组件中使用了我们的 todos 状态数据
const updatedTodoList JSON.stringify([...todos, newTodo]);所以不要忘记将 todo 状态传递给组件
// src/app/page.jsForm todos{todos} setTodos{setTodos} /还有由于我们可以在应用程序中编辑和删除待办事项我们需要相应地更新 localStorage 中的数据。首先将 todos 数据传递给我们的 Item / 组件
// src/components/TODOList.jsxfunction TODOList({ todos, setTodos }) {return (ol classNametodo_list{todos todos.length 0 ? (todos?.map((item, index) (// 将 todos 传递给 Item /Item key{index} item{item} todos{todos} setTodos{setTodos} /))) : (p这里看起来很孤单你在干什么呢/p)}/ol);
}现在我们在我们的 Item / 组件中有了访问待办事项数据的权限我们可以在标记待办事项为已完成后将数据持久化到 localStorage
// src/components/TODOList.jsxconst completeTodo () {setTodos((prevTodos) prevTodos.map((todo) todo.id item.id ? { ...todo, is_completed: !todo.is_completed } : todo));// 标记待办事项为已完成后更新 localStorageconst updatedTodos JSON.stringify(todos);localStorage.setItem(todos, updatedTodos);
};我们还希望在编辑待办事项后将数据持久化到 localStorage
// src/components/TODOList.jsxconst handleInpuSubmit (event) {event.preventDefault();// 编辑待办事项后更新 localStorageconst updatedTodos JSON.stringify(todos);localStorage.setItem(todos, updatedTodos);setEditing(false);
};const handleInputBlur () {// 编辑待办事项后更新 localStorageconst updatedTodos JSON.stringify(todos);localStorage.setItem(todos, updatedTodos);setEditing(false);
};最后我们还希望在删除待办事项后将数据持久化到 localStorage
// src/components/TODOList.jsxconst handleDelete () {setTodos((prevTodos) prevTodos.filter((todo) todo.id ! item.id));// 删除待办事项后更新 localStorageconst updatedTodos JSON.stringify(todos.filter((todo) todo.id ! item.id));localStorage.setItem(todos, updatedTodos);
};这就是你所需要的一切 - 相当容易对吧现在当我们创建新的待办事项时它们将在重新加载我们的应用程序后仍然保留在 localStorage 中。
如何从 localStorage 中读取待办事项数据
尽管我们已经成功将数据持久化到 localStorage但当我们重新加载应用程序或浏览器时我们的应用程序数据仍然会被擦除。这是因为我们尚未利用存储在 localStorage 中的数据。
为了解决这个问题当我们的应用程序被挂载加载时我们希望从 localStorage 中检索数据然后将其传递给我们的状态。
在我们的 src/app/page.js 中我们将从 localStorage 中读取数据并将其存储在我们的 todos 状态中。
// src/app/page.jsuse client;
import React from react;
import Form from /components/Form;
import Header from /components/Header;
import TODOHero from /components/TODOHero;
import TODOList from /components/TODOList;function Home() {const [todos, setTodos] React.useState([]);// 组件挂载时从 localStorage 中检索数据React.useEffect(() {const storedTodos localStorage.getItem(todos);if (storedTodos) {setTodos(JSON.parse(storedTodos));}}, []);const todos_completed todos.filter((todo) todo.is_completed true).length;const total_todos todos.length;return (div classNamewrapperHeader /TODOHero todos_completed{todos_completed} total_todos{total_todos} /Form todos{todos} setTodos{setTodos} /TODOList todos{todos} setTodos{setTodos} //div);
}export default Home;useEffect() 钩子内的代码在组件挂载后运行一次。
这是从 localStorage 中读取数据的部分
const storedTodos localStorage.getItem(todos);由于存储在 localStorage 中的数据是一个字符串我们必须将其转换回我们的对象数组然后才能使用它
JSON.parse(storedTodos)我们完成了。
恭喜经过充满编码和坚持的旅程我们成功地从零构建了一个简单而功能齐备的待办事项应用程序。虽然旅程可能有点漫长但结果是值得的。
您可以在这里探索应用程序的完整源代码。随意深入代码看看它是如何一步步完成的。
感谢您加入我一起进行这次编码冒险。我希望您已经获得了关于使用 React 构建应用程序和使用 localStorage 持久化数据的宝贵见解。
本文视频讲解java567.com