一、功能实现
- 添加任务
- 删除任务
- 更新任务状态
- 全选/全不选
- 清除已完成的任务
1.效果截图
二、组件的划分
- TodoList整体作为一个大组件;
- Header:input添加;
- List:列表作为一个组件;
- Item: 列表中的每个列表项(ListItem)作为一个组件;
- Footer:底部展示和功能作为一个组件;
1.代码编写
App.js 父组件
import './App.css'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
import { useState } from 'react'
function App() {
const todoListData = [
{ id: '001', name: 'vue', check: true },
{ id: '002', name: 'react', check: false },
{ id: '003', name: 'node.js', check: true },
];
const [todoList, setTodoList] = useState(todoListData);
// 添加todo
const addTodo = (todoObj) => {
const newTodos = [todoObj, ...todoList]
setTodoList(newTodos)
};
// 更新todolist
const updataTodo = (id, check) => {
const newTodos = todoList.map((item) => {
if (item.id === id) {
return { ...item, check: check }
} else {
return item
}
})
setTodoList(newTodos)
};
// 删除某项todolist
const delTodo = (id) => {
const newTodos = todoList.filter((item) => {
return item.id !== id
})
setTodoList(newTodos)
};
// 全选/全不选
const allChecked = (check) => {
const newTodos = todoList.map((item) => {
return { ...item, check: check }
})
setTodoList(newTodos)
};
// 清除所以已经完成的任务
const clearAllDone = () => {
const newTodos = todoList.filter((item) => {
return !item.check
})
setTodoList(newTodos)
}
return (
<div style={{display: 'flex', justifyContent: 'center', alignItems: 'center',marginTop: '25px',}}>
<div className="todo-wrap">
<Header addTodo={addTodo}/>
<List todoList={todoList} updataTodo={updataTodo} delTodo={delTodo} />
<Footer todoList={todoList} allChecked={allChecked} clearAllDone={clearAllDone}/>
</div>
</div>
);
}
export default App;
Header 组件
import React from 'react';
import { message } from 'antd';
import './index.css';
const Header = (props) => {
const {
addTodo
} = props;
// 处理input enter事件
const handleKeyUp = (e) => {
if (e.keyCode !== 13) return
if (e.target.value.trim() === '') {
return message.warning('请输入代办事项!');
}
const todoObj = {
id: Math.floor(10000000 * Math.random()),
name: e.target.value,
check: false
}
addTodo(todoObj)
e.target.value = ""
}
return (
<div className="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={(e) => handleKeyUp(e)} />
</div>
)
}
export default Header;
Header.css样式
/*header*/
.todo-header {
margin-bottom: 10px;
}
.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);
}
List 组件
import React from 'react'
import Item from '../Item'
import './index.css'
export default function index(props) {
const {
todoList,
...restProps
} = props;
return (
<ul className="todo-main">
{
todoList.map((item) => <Item item={item} key={item.id} {...restProps}/>)
}
</ul>
)
}
List.css样式
/*list*/
.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 组件
import React, { useState } from 'react';
import { Button } from 'antd';
import './index.css'
const Item = (props) => {
const {
item,
updataTodo,
delTodo
} = props
// 是否展现删除键
const [mouse, setMouse] = useState(false)
// 处理鼠标的移入移除事件
const handleMouse = (mouse) => {
setMouse(mouse)
}
return (
<li onMouseLeave={() => handleMouse(false)} onMouseEnter={() => handleMouse(true)}>
<label>
<input type="checkbox" checked={item.check} onChange={(e) => updataTodo(item.id, e.target.checked)} />
<span>{item.name}</span>
</label>
<Button type="primary" size="small" danger ghost style={{ display: mouse ? 'block' : 'none' }} onClick={()=> delTodo(item.id)}>删除</Button>
</li>
)
}
export default Item;
Item.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: 5px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
Footer 组件
import React from 'react';
import { Button } from 'antd';
import './index.css';
const Footer = (props)=> {
const {
todoList,
allChecked,
clearAllDone,
} = props;
const doneCount = todoList.reduce((pre,todo)=> pre + (todo.check ? 1 : 0),0);
const allCount = todoList.length;
return (
<div className="todo-footer">
<label>
<input type="checkbox" onChange={(e)=>allChecked(e.target.checked)} checked={ doneCount === allCount && allCount !== 0 ? true : false }/>
</label>
<span>
<span>已完成</span> / 全部{todoList.length}
</span>
<Button type="primary" ghost onClick={()=>clearAllDone()}>清除已完成任务</Button>
</div>
)
}
export default Footer;
Footer.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;
}
三、案例总结
1. 动态初始化列表,如何确定将数据放在哪个组件的state中?
- 某个组件使用:放在其自身的state中
- 某些组件使用:放在他们共同的父组件state中(状态提升)
2.父子组件间如何通信
在父子组件中定义改变state数据的方法,将方法以props的形式传递给子组件,在子组件中触发事件处理程序,然后满足某种条件的话就执行父组件传来的函数。
- 【父组件】给【子组件】传递数据:通过props传递
- 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
3.注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
defaultChecked 默认属性 只在初始化数据的时候赋值