使用react实现一个简单的todoList

前言:小编最近基本都是在复习期末考试,自顾不暇了都,自我感觉对react生疏了不少,所以想通过本篇文章简单复习一下react基础语法。本文将带领读者一同探索使用React来构建Todolist的过程。我们将通过学习React的核心概念和组件化开发的思想,逐步构建一个功能完善的Todolist应用。无论您是React初学者还是有一定经验的开发者,本文都将为您提供具体的实现步骤和实用技巧。闲话不多说,直接步入正题...

项目搭建:

js 复制代码
npx create-react-app my-app
cd my-app
npm start

效果如下:

接下来我们做一下基本的调整,构建出todolist的基本样式结构:我们使用 jsx 语法在 App 函数中完成一个 todolist 的基础 html 结构,但是注意在jsx的语法中,样式标签class 变成了 className

js 复制代码
// App.js
import './App.css'

function App() {
	return (
		<div className="App">
			<div className="content">
				<h1 className="header">Todo List</h1>
				<div className="addTodos">
					<input placeholder="Add todo ..." className="input" />
					<button className='add'>Add</button>
				</div>
				<ul className='todoList'>
					<li>
						<span>todo1</span>
						<button>Delete</button>
						<button>Done</button>
					</li>
				</ul>
			</div>
		</div>
	)
}

export default App
js 复制代码
// App.css
body {
	background-color: #eee;
}
.App {
	width: 100vw;
	height: 100vh;
	display: flex;
	justify-content: center;
	align-items: center;
}
.content {
	width: 500px;
	background-color: #fff;
	padding: 10px;
	border-radius: 20px;
}
.content:hover {
	box-shadow: 0 0 50px #aaa;
}

.header {
	font-size: 40px;
}
.input {
	width: 300px;
	height: 30px;
	padding-left: 10px;
	font-size: 18px;
	border-radius: 10px;
}
.add {
	width: 80px;
	height: 35px;
	font-size: 18px;
	margin-left: 10px;
	border-radius: 10px;
}
.lis span {
	margin-right: 10px;
	font-size: 18px;
	display: inline-block;
	width: 50px;
	margin-top: 5px;
}
.lis button {
	height: 30px;
	margin-left: 5px;
	border-radius: 5px;
}
.Del {
	background-color: #de5249;
}
.Done {
	background-color: #5cb85c;
}

调整后的效果如下

在react的框架体系中,秉持的一种思想就是数据驱动视图,也就是通过改变数据进而改变我们的界面,react 中最小的数据管理方案就是使用useState 管理当前组件的 state。现在我们对用户输入的数据state进行管理:

js 复制代码
// App.js

import './App.css'
import { useState } from 'react'

function App() {
	const [todoList, setTodoList] = useState([
		{
			value: 'todo1',
			status: 'active',
		},
	])
	return (
		<div className="App">
			<div className="content">
				<h1 className="header">Todo List</h1>
				<div className="addTodos">
					<input placeholder="Add todo ..." className="input" />
					<button className="add">Add</button>
				</div>
				<ul className="todoList">
					{/* <li>
						<span>todo1</span>
						<button className="Del">Delete</button>
						<button className="Done">Done</button>
					</li> */}
					{
						// 注意,这里要使用 {} ,表示要在jsx中写js表达式,否则报错
						todoList.map((item, index) => {
							return (
								<li key={index} className='lis'>
									<span>{item.value}</span>
									<button className="Del">Delete</button>
									<button className="Done">Done</button>
								</li>
							)
						})
					}
				</ul>
			</div>
		</div>
	)
}

export default App

上面使用的 useState()是react内置的一个Hook函数,它可以接受一个初始值,然后返回一个数组,数组的第一个是当前的 state,第二个是更新 state 的函数,利用 useState(),我们可以实现一个简单的todolist。

现在我们将相关的按钮绑定上函数,处理点击后的相关逻辑,在jsx中我们采用驼峰命名方法进行绑定函数,然后使用花括号包裹住函数,例如:<input onChange={myFunction}>

js 复制代码
// App.js
import './App.css'
import { useState } from 'react'

function App() {
	// 保存todoList的状态
	const [todoList, setTodoList] = useState([
		{
			value: 'todo1',
			status: 'active',
		},
	])
	// 保存input输入框的值
	const [inputVal, setInputVal] = useState('')
	// 更新input的值
	const handleChange = (e) => {
		console.log(e.target.value)
		setInputVal(e.target.value.trim()) // 去掉首尾空格再更新值
	}
	// 添加todo
	const handleAdd = () => {
		if (inputVal) {
			setTodoList(() => {
				const newTodoList = [
					...todoList,
					{
						value: inputVal,
						status: 'active',
					},
				]
				console.log('AddOne', newTodoList)
				return newTodoList
			})
			setInputVal('')
		}
	}
	//删除todo
	const handleDel = (delIndex) => {
		console.log(delIndex)
		setTodoList(() => {
			const newTodoList = todoList.filter(
				(item, index) => index !== delIndex
			)
			return newTodoList
		})
	}
	// 处理todo(active => done && done => active)
	const handleDone = (doneItem, doneIndex) => {
		console.log('doneIndex', doneIndex)
		// 使用map方法而不是forEach方法,forEach方法返回的是undefined,而不是一个新的数组。
		if (doneItem.status === 'active') {
			const newTodoList = todoList.map((item, index) => {
				if (index === doneIndex) item.status = 'done'
				return item
			})
			console.log('activeToDoneList', newTodoList)
			setTodoList(newTodoList)
		} else {
			const newTodoList = todoList.map((item, index) => {
				if (index === doneIndex) item.status = 'active'
				return item
			})
			console.log('doneToActive', newTodoList)
			setTodoList(newTodoList)
		}
	}
	return (
		<div className="App">
			<div className="content">
				<h1 className="header">Todo List</h1>
				<div className="addTodos">
					<input
						onChange={handleChange}
						value={inputVal}
						placeholder="Add todo ..."
						className="input"
					/>
					<button onClick={handleAdd} className="add">
						Add
					</button>
				</div>
				<ul className="todoList">
					{
						// 注意,这里要使用 {} ,表示要在jsx中写js表达式,否则报错
						todoList.map((item, index) => {
							return (
								<li key={index} className="lis">
									<span>{item.value}</span>
									<button
										onClick={() => handleDel(index)}
										className="Del"
									>
										Delete
									</button>
									<button
										onClick={() => handleDone(item, index)}
										className="Done"
									>
										Done
									</button>
								</li>
							)
						})
					}
				</ul>
			</div>
		</div>
	)
}

export default App

随着开发的继续,代码会逐渐增多,如果将代码都放在一个文件中会变得难以维护,所以我们会对组件进行抽离,简单组件抽离就是将单个的功能的代码放在一个文件里面,然后导出给其他文件使用,那么我们将进行如下简单抽离,抽离出来的组件依然是一个函数的形式。addTodo部分和todo部分可以从App当中抽离出来,作为组件在App中渲染:

addTodo.js

js 复制代码
import './addTodo.css'
import { useState } from 'react'

export default function AddTodo() {
	// 保存todoList的状态
	const [todoList, setTodoList] = useState([
		{
			value: 'todo1',
			status: 'active',
		},
	])
	// 保存input输入框的值
	const [inputVal, setInputVal] = useState('')
	// 更新input的值
	const handleChange = (e) => {
		console.log(e.target.value)
		setInputVal(e.target.value.trim()) // 去掉首尾空格再更新值
	}
	// 添加todo
	const handleAdd = () => {
		if (inputVal) {
			setTodoList(() => {
				const newTodoList = [
					...todoList,
					{
						value: inputVal,
						status: 'active',
					},
				]
				console.log('AddOne', newTodoList)
				return newTodoList
			})
			setInputVal('')
		}
	}
	return (
		<div>
			<div className="addTodos">
				<input
					onChange={handleChange}
					value={inputVal}
					placeholder="Add todo ..."
					className="input"
				/>
				<button onClick={handleAdd} className="add">
					Add
				</button>
			</div>
		</div>
	)
}

todo.js

js 复制代码
import './todo.css'
import { useState } from 'react'

export default function Todo() {
	// 保存todoList的状态
	const [todoList, setTodoList] = useState([
		{
			value: 'todo1',
			status: 'active',
		},
	])

	//删除todo
	const handleDel = (delIndex) => {
		console.log(delIndex)
		setTodoList(() => {
			const newTodoList = todoList.filter(
				(item, index) => index !== delIndex
			)
			return newTodoList
		})
	}
	// 处理todo(active => done && done => active)
	const handleDone = (doneItem, doneIndex) => {
		console.log('doneIndex', doneIndex)
		// 使用map方法而不是forEach方法,forEach方法返回的是undefined,而不是一个新的数组。
		if (doneItem.status === 'active') {
			const newTodoList = todoList.map((item, index) => {
				if (index === doneIndex) item.status = 'done'
				return item
			})
			console.log('activeToDoneList', newTodoList)
			setTodoList(newTodoList)
		} else {
			const newTodoList = todoList.map((item, index) => {
				if (index === doneIndex) item.status = 'active'
				return item
			})
			console.log('doneToActive', newTodoList)
			setTodoList(newTodoList)
		}
	}
	return (
		<div>
			<ul className="todoList">
				{
					// 注意,这里要使用 {} ,表示要在jsx中写js表达式,否则报错
					todoList.map((item, index) => {
						return (
							<li key={index} className="lis">
								<span>{item.value}</span>
								<button
									onClick={() => handleDel(index)}
									className="Del"
								>
									Delete
								</button>
								<button
									onClick={() => handleDone(item, index)}
									className="Done"
								>
									Done
								</button>
							</li>
						)
					})
				}
			</ul>
		</div>
	)
}

App.js

js 复制代码
import './App.css'
import AddTodo from './views/AddTodo/addTodo'
import Todo from './views/Todo/todo'

function App() {
	return (
		<div className="App">
			<div className="content">
				<header>
					<h1 className="header">Todo List</h1>
				</header>
				<AddTodo />
				<Todo />
			</div>
		</div>
	)
}

export default App

现在的项目目录结构如下:

但是我们会发现,在AddTodo中点击Add按钮尝试添加一个todo时,下面的todoList并不会渲染出来新增的todo,这是因为小编在抽离组件的时候各自组件都有各自的todoList的状态state,但是我们希望的是他们共享一个state,不同的组件触发的逻辑能够整合在一起,这个要怎么实现呢?这时候就需要使用到 props 把数据传入各个组件内部进行渲染,Props 就是该组件的参数,绑定props对象的方法就是直接在组件外部的jsx上写上其props的名称,例如:

js 复制代码
function Todos(props) {
    // 获取props
    const {
        todos,
        onDone,
        onDelete
    } = props;
}

// 设置props
<Todos
    todos={todolist}
    onDelete={handleDelete}
    onDone={handleDone}
/>

首先还是将 todolist 放在 App.js 这一层组件中,<AddTodo /><Todos /> 可以提供函数操作的 props 对todolist进行操作,进而达到更新的目的,然后重新渲染。

addTodo.js

js 复制代码
import './addTodo.css'
import { useState } from 'react'

export default function AddTodo(props) {
	const { todoList, onAdd } = props
	// 保存input输入框的值
	const [inputVal, setInputVal] = useState('')
	// 更新input的值
	const handleChange = (e) => {
		console.log(e.target.value)
		setInputVal(e.target.value.trim()) // 去掉首尾空格再更新值
	}
	// 将todo传给父组件
	const handleAdd = () => {
		if (inputVal) {
			const newTodoList = [
				...todoList,
				{
					value: inputVal,
					status: 'active',
				},
			]
			onAdd && onAdd(newTodoList)
			setInputVal('')
		}
	}
	return (
		<div>
			<div className="addTodos">
				<input
					onChange={handleChange} //调用父组件传递的函数更新input的值到父组件
					value={inputVal} //绑定input的值
					placeholder="Add todo ..."
					className="input"
				/>
				<button onClick={handleAdd} className="add">
					Add
				</button>
			</div>
		</div>
	)
}

todo.js

js 复制代码
import './todo.css'

export default function Todo(props) {
	const { todoList, onDelete, onDone } = props
	//删除todo,并将的state传递给父组件
	const handleDel = (delIndex) => {
		console.log('delIndex', delIndex)
		const newTodoList =
			todoList && todoList.filter((item, index) => index !== delIndex)
		onDelete && onDelete(newTodoList)
	}
	// 处理todo(active => done && done => active),并将的state传递给父组件
	const handleDone = (doneItem, doneIndex) => {
		console.log('doneIndex', doneIndex)
		// 使用map方法而不是forEach方法,forEach方法返回的是undefined,而不是一个新的数组。
		if (doneItem.status === 'active') {
			const newTodoList = todoList.map((item, index) => {
				if (index === doneIndex) item.status = 'done'
				return item
			})
			console.log('activeToDoneList', newTodoList)
			onDone && onDone(newTodoList)
		} else {
			const newTodoList = todoList.map((item, index) => {
				if (index === doneIndex) item.status = 'active'
				return item
			})
			console.log('doneToActive', newTodoList)
			onDone && onDone(newTodoList)
		}
	}
	return (
		<div>
			<ul className="todoList">
				{
					// 注意,这里要使用 {} ,表示要在jsx中写js表达式,否则报错
					todoList.map((item, index) => {
						return (
							<li key={index} className="lis">
								<span>{item.value}</span>
								<button
									onClick={() => handleDel(index)}
									className="Del"
								>
									Delete
								</button>
								<button
									onClick={() => handleDone(item, index)}
									className="Done"
								>
									Done
								</button>
							</li>
						)
					})
				}
			</ul>
		</div>
	)
}

App.js

js 复制代码
import './App.css'
import AddTodo from './views/AddTodo/addTodo'
import Todo from './views/Todo/todo'
import { useState } from 'react'

function App() {
	// 保存todoList的状态
	const [todoList, setTodoList] = useState([])
	// 处理addTode组件传递过来的新的todo
	const onAdd = (newTodoList) => {
		if (newTodoList.length >= 0) {
			//todo都删掉后长度为0,即Todo组件传递过来的数组长度为0时也是允许的
			setTodoList(newTodoList)
		}
	}
	//删除todo
	const onDelete = (newTodoList) => {
		setTodoList(newTodoList)
	}
	// 处理todo(active => done && done => active)
	const onDone = (newTodoList) => {
		setTodoList(newTodoList)
	}

	return (
		<div className="App">
			<div className="content">
				<header>
					<h1 className="header">Todo List</h1>
				</header>
				<AddTodo todoList={todoList} onAdd={onAdd} />
				<Todo todoList={todoList} onDelete={onDelete} onDone={onDone} />
			</div>
		</div>
	)
}

export default App

为使用props前的效果,增加一个todo时,下面的todoList并不会渲染出最新的todoList数据:

这是完善props之后的效果:

其实有react基础的小伙伴不难发现,只要我们刷新网页,这里的todoList的数据就会丢失,但是我们通常不希望这样,这里我们简单地使用localStorage对数据进行持久化处理在这里我们需要借助useEffect这个Hook实现,useEffect 的第一个函数参数是我们要执行的一些操作,第二个参数是一个依赖数组,如果数组为空,则在组件渲染初期运行一次第一个参数函数,如果数组内部有值,则当里面的某一个值发生变化就执行一次第一个参数函数。在这里我们将todolist存在localstorage,然后在第一次加载的时候去获取历史记录,在todolist发生改变的时候存储当前todolist。

现在我们对 App.js 的代码进行修改:

js 复制代码
import './App.css'
import AddTodo from './views/AddTodo/addTodo'
import Todo from './views/Todo/todo'
import { useState, useEffect } from 'react'

function App() {
	// 保存todoList的状态
	const [todoList, setTodoList] = useState([])
	const getTodoList = () => {
		const todos = localStorage.getItem('todos')
		if (todos) {
			return JSON.parse(todos)
		}
		return []
	}
	useEffect(() => {
		// 从数据存储源获取数据,一般这里会从后端获取数据
		setTodoList(getTodoList)
	}, [])
	// 处理addTode组件传递过来的新的todo
	const onAdd = (newTodoList) => {
		if (newTodoList.length >= 0) {
			//todo都删掉后长度为0,即Todo组件传递过来的数组长度为0时也是允许的
			setTodoList(newTodoList)
			localStorage.setItem('todos', JSON.stringify(newTodoList))
		}
	}
	//删除todo
	const onDelete = (newTodoList) => {
		setTodoList(newTodoList)
		localStorage.setItem('todos', JSON.stringify(newTodoList))
	}
	// 处理todo(active => done && done => active)
	const onDone = (newTodoList) => {
		setTodoList(newTodoList)
		localStorage.setItem('todos', JSON.stringify(newTodoList))
	}

	return (
		<div className="App">
			<div className="content">
				<header>
					<h1 className="header">Todo List</h1>
				</header>
				<AddTodo todoList={todoList} onAdd={onAdd} />
				<Todo todoList={todoList} onDelete={onDelete} onDone={onDone} />
			</div>
		</div>
	)
}

export default App

现在我们刷新网页todoList的数据就不会消失啦!!!

结语:到这里就基本结束啦,原码我贴在下方啦,需要的小伙伴可以clone一下:

源码地址传送门

文章参考博主web前端的markzzw)

相关推荐
Larcher8 分钟前
新手也能学会,100行代码玩AI LOGO
前端·llm·html
徐子颐20 分钟前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭33 分钟前
如何理解HTML语义化
前端·html
jump6801 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信1 小时前
我们需要了解的Web Workers
前端
brzhang1 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu1 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花1 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋2 小时前
场景模拟:基础路由配置
前端
六月的可乐2 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程