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

接下来我们做一下基本的调整,构建出todolist的基本样式结构:我们使用 jsx 语法在 App 函数中完成一个 todolist 的基础 html 结构,但是注意在jsx的语法中,样式标签class
变成了 className
。
// 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
// 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进行管理:
// 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}>
// 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
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
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
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的名称,例如:
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
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
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
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
的代码进行修改:
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一下: