将整个案列拆分成了4个组件
publick文件夹
index:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="stylesheet" href="./css/bootstrap.css"/>
<title>react脚手架</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
src 文件夹
app.jsx
import React, { Component } from 'react'
import Header from './components/Header'
import Footer from './components/Footer'
import List from './components/List'
import './App.css'
export default class App extends Component {
//状态在哪里,操作状态的方法就在哪里
state = {todos:[
{id:'001',name:'吃饭',done:true},
{id:'002',name:'睡觉',done:true},
{id:'003',name:'打代码',done:false},
]}
//addTodo用于添加一个todo,接受的参数是todo对象
addTodo = (todoObj) =>{
//获取原todos
const {todos} = this.state
//追加一个todo
const newTodos = [todoObj,...todos]
//更新状态
this.setState({todos:newTodos})
}
//updatetodo用于更新一个todo对象
updateTodo = (id,done) =>{
//获取状态中的todos
const {todos} = this.state
//匹配处理数据
const newTodos = todos.map((todoObj)=>{
if(todoObj.id === id)return {...todoObj,done}
else return todoObj
})
this.setState({todos:newTodos})
}
//用于删除todo对象
deleteTodo = (id) =>{
//获取原来的todos
const{todos} = this.state
const newTodos = todos.filter((todoObj)=>{
return todoObj.id!== id
})
this.setState({todos:newTodos})
}
//checkAllTodo用于全选
checkAllTodo = (done)=>{
//获取原来的todos
const {todos} = this.state
//加工数据
const newTodos = todos.map((todoObj)=>{
return {...todoObj,done}
})
//更新状态
this.setState({todos:newTodos})
}
//用于清除所有已完成的事件
clearAllDone=()=>{
//获取原来todos
const {todos} = this.state
//过滤数据
const neewTodos = todos.filter((todoObj)=>{
return !todoObj.done
})
//更新状态
this.setState({todos:neewTodos})
}
render() {
const {todos} = this.state
return (
<div>
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={this.addTodo}/>
<List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone ={this.clearAllDone}/>
</div>
</div>
</div>
)
}
}
app.css
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
comment-组件部分
头部组件Header
import React, { Component } from 'react'
// import PropTypes from 'prop-types'
import {nanoid} from 'nanoid'
export default class Header extends Component {
//对接受的props进行类型,以及必要性的限制
// static PropTypes ={
// addTodo:PropTypes.func.isRequired
// }
handleKeyUp = (event)=>{
if(event.keyCode!== 13) return
//添加的todo不能为空
if(event.target.value.trim() === ''){
alert('输入不能为空')
return
}
//准备好一个todo对象
const todoObj = {id:nanoid(),name:event.target.value,done:false}
//将todoobj传递给APP
this.props.addTodo(todoObj)
//清空输入
event.target.value = ''
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
)
}
}
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.arr.isRequired,
// updateTodo: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中的每一列,ITEM组件
import React, { Component } from 'react'
export default class item extends Component {
state = {mouse:false}//标识鼠标移入移出
handleMouse = (flag) =>{
return ()=>{
this.setState({mouse:flag})
}
}
//勾选,取消勾选某一个todo的回调
handleCheck = (id)=>{
return (event)=>{
this.props.updateTodo(id,event.target.checked)
}
}
handleDelet = (id)=>{
if(window.confirm('确定删除吗?')){
this.props.deleteTodo(id)
}
}
render() {
const{id,name,done} = this.props
const{mouse} = this.state
return (
<div>
<li style={{backgroundColor:mouse?'#ddd':'white'}} onMouseLeave={this.handleMouse(false)} onMouseEnter={this.handleMouse(true)}>
<label>
<input type="checkbox" checked={done} onChange={this.handleCheck(id)}/>
<span>{name}</span>
</label>
<button onClick={()=>(this.handleDelet(id))} className="btn btn-danger" style={{display:mouse?'block':'none'}}>删除</button>
</li>
</div>
)
}
}
最终,尾部组件
import React, { Component } from 'react'
export default class Footer extends Component {
//全选checkbox的回调
handleCheckAll = (event)=>{
this.props.checkAllTodo(event.target.checked)
}
handleClearAllDone = ()=>{
this.props.clearAllDone()
}
render() {
const {todos} = this.props
//计算已完成的个数
const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
//总数
const total = todos.length
return (
<div className="todo-footer">
<label>
<input type="checkbox" onChange={this.handleCheckAll} checked={doneCount===total&&total !==0?true:false}/>
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
</div>
)
}
}