一、根据静态页面将代码进行拆分
把初始的静态界面拆分为四个组件,分别为Header、List、Item、Footer,注意拆分时修改代码的格式,如class="btn btn-danger"改为className=“btn btn-danger”
二、传递初始State
1、创建初始数据并传递给List
在APP.jsx中加入初始的数据,因为这些数据需要List通过Item进行最开始的展示,所以将数据传递给List。
state = {todos:[
{id:'001',name:'eat',done:true},
{id:'002',name:'sleep',done:true},
{id:'003',name:'work',done:false}
]}
render() {
const {todos} = this.state
return (
<div className="todo-container">
<div className="todo-wrap">
<Header/>
<List todos={todos}/>
<Footer/>
</div>
</div>
)
}
2、List与Item接收初始数据
List接收数据后通过map方法循环todos中的数据形成Item组件,在map中key值取todos最开始定义的id即可,同时将todos内的数据传递给Item。
const {todos} = this.props
return (
<ul className="todo-main">
{
todos.map( todo =>{
return <Item key={todo.id} {...todo}/>
})
}
</ul>
)
Item同理,接收数据后放入Li标签内(id目前暂未用到,所以先去掉),这里可以使用defaultChecked方法来通过初始的done来确定是否勾选前面的checkbox,但使用defaultChecked会有一些问题,后面再改掉。
const {name,done} = this.props
return (
<li>
<label>
<input type="checkbox" defaultChecked={done}/>
<span>{name}</span>
</label>
<button className="btn btn-danger" style={{'display':'none'}}>删除</button>
</li>
)
效果如下:
三、Header添加待办事项
1、确定回车的keyCode
通过onKeyUp(onkeyup 事件会在键盘按键被松开时发生)来输出一下按键的keyCode,确定回车的keyCode。
handleKeyUp =(event)=>{
console.log(event.target.value,event.keyCode);//控制台观察回车的keyCode为13
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
)
}
2、Header组件修改App的state
因为Header是App的子组件,所以如果想要修改父组件的数据,可以在App中向Header传递一个函数,在Header中使用这个函数修改App的state,就完成了子组件向父组件传递数据的过程。
App.jsx
addTodos = (todoObj) => {
const { todos } = this.state
const newTodos = [todoObj,...todos]
this.setState({todos:newTodos})
}
...
...
...
//向Header中传递函数
<Header addTodos={this.addTodos} />
···
Header.jsx
handleKeyUp = (event) => {
const { keyCode, target } = event //解构赋值
if (keyCode !== 13) return //判断是否为回车
if (target.value.trim() === '') { //校验输入不可以为空值
alert('输入不能为空!')
return
}
const todoObj = { id: nanoid(), name: target.value, done: false } //id通过nanoid来设置,也可以用random设置
this.props.addTodos(todoObj)
target.value = '' //添加结束后把输入栏置为空
}
...
...
...
//向Header中传递函数
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
···
四、鼠标移入移出效果
使用onMouseEnter与onMouseLeave完成添加鼠标移入与移出效果:在Item中添加mouse的状态,通过函数设置该状态为true或者false来控制鼠标移入移出时Li的背景色变化,同时删除按钮也根据mouse的状态来进行展示或者隐藏。
state = { mouse: false }
handleMouse = (flag) => {
return () => { //需要返回函数,否则下面会一直调用handleMouse
this.setState({ mouse: flag })
}
}
···
···
···
<li style={{ backgroundColor: mouse ? '#ddd' : 'white' }} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
···
<button onClick={()=> this.handleDelete(id) } className="btn btn-danger" style={{display:mouse?'block':'none'}}>删除</button>
···
五、勾选事件与取消勾选事件、添加props限制
1、勾选事件与取消勾选事件
checkbox在Item当中,所以如果想要通过勾选来改变事件的done值的话,需要在checkbox当中添加onChange方法。因为Item相当于App的“孙子组件”,所以这里想要修改事件的done值的话,还是需要在App当中加入更新todos的函数来进行修改,然后传递给List,List再传递给Item,过程类似于增加一个todo。
App.jsx
updateTodos = (id,done)=>{
const{todos} = this.state
const newTodos = todos.map((todoObj)=>{
if(todoObj.id === id) return {...todoObj,done:done}
else return todoObj//根据id来修改done的状态,上面done:done可以简写为done
})
this.setState({todos:newTodos})
}
List.jsx
const {todos,updateTodos} = this.props
return (
<ul className="todo-main">
{
todos.map( todo =>{
return <Item key={todo.id} {...todo} updateTodos={updateTodos}/>
})
}
</ul>
)
Item.jsx
handleCheck = (id) => {
return (event) => {
this.props.updateTodos(id,event.target.checked) //传回id与勾选框的状态
}
}
···
···
···
<input type="checkbox" defaultChecked={done} onChange={this.handleCheck(id)} /> //传入id,根据id来修改App中state的对应值
···
2、限制props
添加对props的限制,没装的话需要先安装一下。例子如下:
import PropTypes from 'prop-types'
···
static propTypes = {
todos:PropTypes.array.isRequired,
updateTodo:PropTypes.func.isRequired,
deleteTodo:PropTypes.func.isRequired,
}
六、删除todo
与添加todo类似,同样是在App中完成deleteTodos函数,传递到Item中使用,注意删除前使用confirm来确定是否删除。
App.jsx
deleteTodos = (id)=>{
const{todos}=this.state
const newTodos = todos.filter((todoObj)=>{
return todoObj.id!==id
})
this.setState({todos:newTodos})
}
Item.jsx
handleDelete = (id) => {
if (window.confirm('确定删除吗')) {
this.props.deleteTodos(id)
}
}
···
<button onClick={() => this.handleDelete(id)} className="btn btn-danger" style={{ 'display': mouse ? 'block' : 'none' }}>删除</button>
七、全选与删除已完成事件
1、全选
先要完成Footer中 已完成/全部 的功能,这里全部的数值直接取todos的长度,已完成的数值通过reduce方法来判断已完成事件的数量。
const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
const total = todos.length
reduce函数
arr.reduce(function(prev,cur,index,arr){
...
}, init);
-
参数
- prev 必需。累计器累计回调的返回值; 表示上一次调用回调时的返回值,或者初始值 init;
- cur 必需。表示当前正在处理的数组元素;
- index 可选。表示当前正在处理的数组元素的索引,若提供 init 值,则起始索引为- 0,否则起始索引为1;
- arr 可选。表示原数组;
- init 可选。表示初始值。
原文链接:https://blog.csdn.net/qq_38970408/article/details/121018660
因为defaultChecked只在第一次起作用,所以完成 已完成/全部的展示功能后,给Footer对应的checkbox添加onChange方法,同时添加checked方法(checked必须有onChange方法)。同时将前面Item中checkbox的也改为checked用法。
checkAll函数
这个函数包括全选以及全不选。Footer中的checkAll方法要传入checkbox的check状态,这样在App中就可以根据这个状态来调整Item中全部checkbox的完成状态。
当Item中的checkbox全部勾选时,也要改变Footer中checkbox的状态,这个通过checked方法实现就可以。
Footer.jsx
。
checkAll = (event)=>{
this.props.checkAll(event.target.checked)
}
···
···
···
<input type="checkbox" onChange={this.checkAll} checked={doneCount === total && total !== 0 ? true : false} />
App.jsx
。
const {todos} = this.state
const newTodos = todos.map((todoObj)=>{
return {...todoObj,done}
})
this.setState({todos:newTodos})
2、清除完成事件
跟删除类似,Footer的button使用App传入的方法,在App的方法中返回done值为false的事件即可。
App.jsx
。
clearAll=()=>{
const{todos} = this.state
const newTodos = todos.filter((todoObj)=>{
return !todoObj.done
})
this.setState({todos:newTodos})
}