使用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)

相关推荐
程序猿小D43 分钟前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
奔跑吧邓邓子1 小时前
npm包管理深度探索:从基础到进阶全面教程!
前端·npm·node.js
前端李易安2 小时前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp
汪子熙2 小时前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ2 小时前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.6 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。8 小时前
案例-表白墙简单实现
前端·javascript·css
数云界8 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd8 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常8 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine