index.js
import React, { Component } from 'react'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
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},
{id:'004',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})
}
//deleteTodo用于删除一个todo对象
deleteTodo = (id)=>{
//获取原来的todos
const {todos} = this.state
//删除指定id的todo对象
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用于清除所有已完成的
clearAllDone = ()=>{
//获取原来的todos
const {todos} = this.state
//过滤数据
const newTodos = todos.filter((todoObj)=>{
return !todoObj.done
})
//更新状态
this.setState({todos:newTodos})
}
render() {
const {todos} = this.state
return (
<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>
)
}
}
app.jsx
import React, { Component } from 'react'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
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},
{id:'004',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})
}
//deleteTodo用于删除一个todo对象
deleteTodo = (id)=>{
//获取原来的todos
const {todos} = this.state
//删除指定id的todo对象
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用于清除所有已完成的
clearAllDone = ()=>{
//获取原来的todos
const {todos} = this.state
//过滤数据
const newTodos = todos.filter((todoObj)=>{
return !todoObj.done
})
//更新状态
this.setState({todos:newTodos})
}
render() {
const {todos} = this.state
return (
<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>
)
}
}
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;
}
创造components文件夹
下面的四个文件夹在components里面
1.Header
index.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {nanoid} from 'nanoid'
import './index.css'
export default class Header extends Component {
//对接收的props进行:类型、必要性的限制
static propTypes = {
addTodo:PropTypes.func.isRequired
}
//键盘事件的回调
handleKeyUp = (event)=>{
//解构赋值获取keyCode,target
const {keyCode,target} = event
//判断是否是回车按键
if(keyCode !== 13) return
//添加的todo名字不能为空
if(target.value.trim() === ''){
alert('输入不能为空')
return
}
//准备好一个todo对象
const todoObj = {id:nanoid(),name:target.value,done:false}
//将todoObj传递给App
this.props.addTodo(todoObj)
//清空输入
target.value = ''
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
)
}
}
index.css
/*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);
}
2.List
index.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'
export default class List extends Component {
//对接收的props进行:类型、必要性的限制
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>
)
}
}
index.css
/*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;
}
3.Item
index.jsx
import React, { Component } from 'react'
import './index.css'
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)
}
}
//删除一个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>
)
}
}
index.css
/*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;
}
4.Footer
index.jsx
import React, { Component } from 'react'
import './index.css'
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>
)
}
}
index.css
/*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;
}