持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21 天,点击查看活动详情
动态组件
App
由于Header
,Body
,Footer
等组件为兄弟组件,相互间无法进行参数传递,我们需要通过他们父组件App
来实现组件间的数据传递。由于state
是定义在App
组件中,那么我们针对该状态的操作函数都在该组件中进行实现。
class App extends Component {
state = {
todos: [
{ id: 1, name: 'eat', done: true },
{ id: 2, name: 'sleep', done: false },
{ id: 3, name: 'code', done: false },
{ id: 4, name: 'juejin', done: true },
]
}
// 添加任务
addTodo = (todo) => {
const { todos } = this.state
this.setState({ todos: [todo, ...todos] })
}
// 更改todo状态
updateTodo = (id, done) => {
const { todos } = this.state
this.setState({
todos: todos.map(todo => {
if (todo.id === id) {
return { ...todo, done }
} else {
return todo
}
})
})
}
// 批量更改todo状态
updateTodos = (done) => {
const { todos } = this.state
this.setState({
todos: todos.map(todo => {
return {...todo, done}
})
})
}
// 删除todo
deleteTodo = (id) => {
const { todos } = this.state
this.setState({
todos: todos.filter(todo => todo.id !== id)
})
}
// 删除所有已完成todo
deleteAllByDone = () => {
const {todos} = this.state
this.setState({todos: todos.filter(todo => !todo.done)})
}
render() {
const { todos } = this.state
return (
<div id="root">
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={this.addTodo} />
<Body todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
<Footer todos={todos} updateTodos={this.updateTodos} deleteAllByDone={this.deleteAllByDone}/>
</div>
</div>
</div>
)
}
}
Header
具体实现功能:
- 给
input
输入框添加按键弹起回调事件,获取到输入的值; - 通过
keyCode
判断当用户触发了回车键,则将当前输入框内的值传递给Todo
列表。
export default class Header extends Component {
// 对接收的参数类型,必要性进行限制
static propTypes = {
addTodo: PropTypes.func.isRequired
}
// 按钮弹起事件
handleKeyUp = (event) => {
// 得到按键Code和目标值
const {keyCode, target} = event
if(target.value.trim() === ''){
alert('输入内容不鞥为空!')
return
}
// 判断是否回车
if(keyCode === 13){
// 调用父组件的添加任务函数,将参数传递给父组件
this.props.addTodo({
id: nanoid(),
name: target.value,
done: false
})
target.value = ''
}
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入任务名称,按回车键确认" />
</div>
)
}
}
Body
具体实现功能:
- 获取到祖父件中的
Todo
列表,将其渲染到页面中。 - 将父组件都函数传递给
Item
组件,实现具体功能。
export default class Body 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 {...todo} key={todo.id} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
})
}
</ul>
)
}
}
Item
具体实现功能:
- 将
todo
解析渲染,把对应任务展示出来; - 当鼠标移入时给其添加一个灰色的背景色,同时显示删除按钮,在鼠标移出时恢复;
- 在我们勾选或者取消勾选的时候,需要更新当前
todo
的done
值; - 点击删除按钮提示是否删除,确定删除则删除当前行
todo
。
export default class Item extends Component {
state = ({ mouse: false })
// 鼠标移动状态控制
handlerMouse = (flag) => {
return () => {
this.setState({ mouse: flag })
}
}
// 复选框回调
handleCheck = (id) => {
return (event) => {
this.props.updateTodo(id, event.target.checked)
}
}
// 删除按钮回调
handleClick = (id) => {
// confirm不能直接写,需要使用window来调用
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.handlerMouse(true)} onMouseLeave={this.handlerMouse(false)} >
<label>
<input onChange={this.handleCheck(id)} type="checkbox" checked={done} />
<span>{name}</span>
</label>
<button className="btn btn-danger" style={{ display: mouse ? 'block' : 'none' }} onClick={() => this.handleClick(id)}>删除</button>
</li>
)
}
}
Footer
具体实现功能:
- 当所有
todo
被选中时,全选按钮需要勾上; - 点击全选/全部取消,上方的
todo
勾选框也要跟着变化; - 点击删除全部已完成按钮,需要清除勾选的
todo
。
export default class Footer extends Component {
// 全选按钮回调
handleCheck = (event) => {
this.props.updateTodos(event.target.checked)
}
// 全部删除按钮回调
handlerClick = () => {
this.props.deleteAllByDone()
}
render() {
const { todos } = this.props
// 计算已完成数量
const doneCount = todos.reduce((pre, todo) => { return todo.done ? pre + 1 : pre }, 0)
const total = todos.length
return (
<div className="todo-footer">
<label>
<input type="checkbox" onChange={this.handleCheck} checked={doneCount === total} />
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button className="btn btn-danger" onClick={this.handlerClick}>清除已完成任务</button>
</div>
)
}
}