青训营学习笔记
01 代办事项(TodoList)简述
TodoList案例是前端经典案例之一,代办事项(TodoList)案例通常由一个输入框和一个列表组成。主要的功能是用户可以在输入框中输入待办事项,并点击提交按钮将其添加到列表中。用户还可以标记已完成的任务、删除任务以及对任务进行编辑等操作。这个案例可以帮助开发者巩固对HTML、CSS和JavaScript的理解,并学习如何操作DOM元素、处理用户输入以及进行状态管理等。在React的学习中,通过TodoList案例,我学到了很多React设计思路,在此记录一下React的待办事项案例学习过程。
02 实现静态页面
在create-react-app中编写代码和在vue-cli中是很不相同的,对于页面原型的style样式的CSS代码便不再着重记录。
首先,App.js中有整体组件代码布局:
import React, { Component } from 'react'
import './App.css'
import Header from './component/Header'
import List from './component/List'
import Footer from './component/Footer'
export default class App extends Component {
render() {
return (
<div className='todo-container'>
<div className='todo-wrap'>
<Header/>
<List/>
<Footer/>
</div>
</div>
)
}
}
在代码中,也可以看出组件的拆分思路,本次案例代码中,组件拆分情况如下,可供参考:
在App中初始化状态(state):
state = {
todos:[
{id:'001', name:'吃饭', done:false},
{id:'002', name:'睡觉', done:true},
{id:'003', name:'打豆豆', done:true},
{id:'004', name:'敲代码', done:false},
{id:'005', name:'写论文', done:true},
]
}
03 Header组件功能实现
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { nanoid } from 'nanoid';
export default class Header extends Component {
static propTypes = {
addTodo: PropTypes.func.isRequired
}
handleKeyUp = (event) => {
const {key, target} = event;
if(key !== 'Enter') return
if(target.value.trim() === ''){
alert('输入不能为空')
return
}
//准备好一个要添加的todo对象
const todoObj = {id:nanoid(), name:target.value, done:false};
//向父组件App传递todoObj
this.props.addTodo(todoObj);
target.value = '';
}
render() {
return (
<div className='todo-header'>
<input onKeyUp={this.handleKeyUp} type="text" placeholder='请输入任务名称,按回车键确认' />
</div>
)
}
}
-
在此组件中,由于creat-react-app没有进行props限制的前置规定,所以在对传入的prop进行限制的时候,需引入名为
props-type的库,执行命令:yarn add prop-types -
在Header组件中,需要实现添加一个todo的功能,但受限于在书写案例时,没有服务器来为我们提供新的todo的ID,因此,使用
nanoid进行ID的生成。 -
同时,准备好一个要添加的todo对象,并向父组件传递一个todo对象,通过App父组件
addTodo()函数定义来实现添加一个代办事项:addTodo = (todoObj) => { //获取原todos const {todos} = this.state; //追加一个todo const newTodos = [todoObj,...todos] //更新状态 this.setState({todos:newTodos}) }
04 List组件功能实现
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import Item from '../Item'
export default class List extends Component {
static propTypes = {
todos: PropTypes.array.isRequired,
updateTodo: PropTypes.func.isRequired,
deleteTodo: PropTypes.func.isRequired
}
render() {
const {todos, updateTodo, deleteTodo} = this.props;
return (
<ul className='todo-main'>
{
todos.map((todo)=>{
return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
})
}
</ul>
)
}
}
- 在List组件中,同样对传入的props进行了类型和必要性的限制:
.array.isRequired就是要求传入的类型为数组,且必须传入值,不能为空.func即传入的数据类型为函数
- 在对Item组件进行展示时,使用了
...JS中的展开运算符
05 Item组件的功能实现
import React, { Component } from 'react'
export default class Item extends Component {
state = {
mouse:false
}
//鼠标移入移出的回调
handleMouse = (flag) => {
return () => {
this.setState({mouse:flag});
}
}
handleCheck = (id) => {
return (event) => {
this.props.updateTodo(id,event.target.checked);
}
}
//删除一个todo的回调
handleDelete = (id) => {
if(window.confirm('确定删除吗?')){
this.props.deleteTodo(id);
}
}
render() {
const {id, name, done} = this.props;
const {mouse} = this.state;
return (
<li style={{backgroundColor:mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
<label>
<input type='checkbox' checked={done} onChange={this.handleCheck(id)}/>
<span>{name}</span>
</label>
<button onClick={ ()=>{this.handleDelete(id)} } className='btn btn-danger' style={{display:mouse ? 'block' : 'none'}}>删除</button>
</li>
)
}
}
-
在此组件中,定义了一个鼠标移入移出的回调来处理每个代办的样式
-
在每一个代办事项中,应有勾选复选框时,对应的代办事项的done值也发生改变,因此定义了
handleCheck()函数。 -
同时,在Item组件中,也应实现删除对应的代办事项功能,在此定义了删除一个todo的回调函数,在父组件App中进行数据处理并进行回调:
deleteTodo = (id) => { const {todos} = this.state; //删除指定id的todo对象 const newTodos = todos.filter((todoObj) => { return todoObj.id !== id }) this.setState({todos:newTodos}) }
06 Footer组件的功能实现
import React, { Component } from 'react'
export default class Footer extends Component {
handleCheckAll = (event) => {
this.props.checkAllTodo(event.target.checked)
}
handleClearAllDone = () => {
this.props.clearAllDone()
}
render() {
const {todos} = this.props;
const doneCount = todos.reduce((pre,todo) => {return pre + (todo.done ? 1 : 0)},0);
const total = todos.length;
return (
<div className='todo-footer'>
<label>
<input type='checkbox' checked={doneCount === total && total ? true : false} onChange={this.handleCheckAll}/>
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button onClick={this.handleClearAllDone} className='btn btn-danger' >清除已完成任务</button>
</div>
)
}
}
-
在Footer组件中,首先要实现的功能是统计已完成的待办数和总的待办数,并进行展示。因此,在代码中,定义了
doneCount和total分别代表二者,并通过数组的reduce方法进行已完成个数的统计 -
对于清楚所有已完成代办事项的功能实现,我们定义了一个名为
handleClearAllDone()的函数来处理clearAllDone()的函数回调。并在App组件里定义好回调的函数:clearAllDone = () => { const {todos} = this.state; const newTodos = todos.filter((todoObj) => { return !todoObj.done }) this.setState( {todos:newTodos} ) } -
同时,对于全选代办事项的功能,我们定义了名为
handleCheckAll的回调,在App组件定义好函数实现功能:checkAllTodo = (done) => { const {todos} =this.state const newTodos = todos.map((todoObj) => { return {...todoObj, done} }) this.setState( {todos:newTodos} ) }