react 制作一个 todoList 组件
上一篇博文我们说了 react 脚手架创建并使用一个组件,OK,那么重点来喽,每个框架学习的必做案例,todoList 功能。
todoList 案例
今天就实现一个 网页版的 todoList 案例效果。
- 头部输入框,输入待办事件后点击回车添加到列表。如果输入为空点击回车弹出提示;
- 列表鼠标移入显示删除按钮,点击按钮删除该待办事件;
- 勾选复选框,底部已完成数据显示联动,全部勾选则底部勾选框选中,其他情况不选中;
- 点击底部清楚已选择任务,则将勾中数据全部清楚。
拆分组件
接下来是怎么拆分组件比较好,每个人的拆分习惯是不一样的,我们本着一个原则,组件拆分有必要的,每个组件完成相应的功能,不能拆分过于粗糙,也不能拆分过于细致。
你看我们能不能这么拆分。
编写代码
OK,我们就按照上面图片拆分的组件进行编写静态代码,拆分出:
- 头部组件 Header:用于展示输入框,输入内容,向列表添加待办事件。
- 列表组件 List:用于展示待办事件列表。
- 待办事件组件 Item:用于展示待办事件项,实现勾选,删除功能。
- 尾部组件 Footer:用于展示底部全选框、已经勾选数据和删除按钮。
然后就可以开发静态代码,记住,组件开发的时候,先把静态页面写完在想数据怎么交互,不要一边搓页面一边实现,这样不好,当然熟练了怎么样都可以。
我写博客一点一点分析不方便,然后我省事,直接把所有的代码全部粘过来了就,没啥技术难点,之前20篇博客已经把里面设计到的东西都说过了,所以说从这个 react 专栏按顺序看到这里的话,案例里面的代码没有难度。
App组件
组件代码:
// 创建外壳组件
import React, { Component } from "react";
import Header from "./components/Header";
import List from "./components/List";
import Footer from "./components/Footer";
import './App.css'
// 创建并暴露APP组件
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 },
]
}
// 用于接收一个todo,参数是todo对象
addTodo = (todoObj) => {
// 获取原来的 todos
let { todos } = this.state
const newTodos = [todoObj, ...todos]
this.setState({ todos: newTodos })
}
// 勾选与取消勾选todos
updateTodo = (id, done) => {
const { todos } = this.state
let newTodos = todos.map((todo) => {
if (todo.id === id) return { ...todo, done }
else return todo
})
this.setState({ todos: newTodos })
}
// 删除 todo 数据
deleteTodo = (id) => {
const { todos } = this.state
let newTodos = todos.filter(todo => {
return todo.id !== id
})
this.setState({ todos: newTodos })
}
// 全选
checkAllTodo = (done) => {
const { todos } = this.state
let newTodos = todos.map((todo) => {
return { ...todo, done }
})
this.setState({ todos: newTodos })
}
// 清除所有已勾选的
clearALLDone = () => {
const { todos } = this.state
let newTodos = todos.filter(todo => {
return !todo.done
})
this.setState({ todos: newTodos })
}
render() {
let { 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>
)
}
}
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 组件
组件代码:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { v4 as uuidv4 } from "uuid"
import './index.css'
export default class Header extends Component {
// 对接收到props的数据进行类型和必要性的限制
static propTypes = {
addTodo: PropTypes.func.isRequired
}
// 键盘抬起事件
handleKeyUp = (event) => {
// 解构赋值获取keyCode和target
let { keyCode, target } = event
// 判断是否点击的回车键
if (keyCode !== 13) return
// 输入为空判断
if (target.value.trim() === '') {
alert("输入内容不能为空!")
return
}
// 准备todo对象
const todoObj = {
id: uuidv4(),
name: target.value,
done: false
}
this.props.addTodo(todoObj)
target.value = ''
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"></input>
</div>
)
}
}
样式代码:
/*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);
}
List 组件
组件代码:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item/index'
import './index.css'
export default class List extends Component {
// 对接收到props的数据进行类型和必要性的限制
static propTypes = {
todos: PropTypes.array.isRequired,
updateTodo: PropTypes.func.isRequired
}
render() {
let { todos, updateTodo, deleteTodo } = this.props
return (
<ul className="todo-main">
{
todos.map((todo) => {
return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo} />
})
}
</ul>
)
}
}
样式代码:
/*main*/
.todo-main {
margin-top: 10px;
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, { Component } from 'react'
import './index.css'
export default class Item extends Component {
state = { mouse: false }
// 鼠标移入移除函数
handleMouse = (flag) => {
return () => {
this.setState({ mouse: flag })
}
}
// 用于勾选todo改变状态
handleChange = (id) => {
return (event) => {
this.props.updateTodo(id, event.target.checked)
}
}
// 删除某一数据的回调
handleDelete = (id) => {
if (window.confirm("确认删除吗?")) {
this.props.deleteTodo(id)
}
}
// 渲染组件
render() {
let { name, id, done } = this.props
let { mouse } = this.state
return (
<li style={{ background: mouse ? '#ddd' : '#fff' }} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
<label>
<input type="checkbox" checked={done} onChange={this.handleChange(id)}></input>
<span>{name}</span>
</label>
<button onClick={() => this.handleDelete(id)} className="btn btn-danger" style={{ display: mouse ? 'block' : 'none' }}>删除</button>
</li>
)
}
}
样式代码:
/*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 组件
组件代码:
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 doneTotal = todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
// 总数
const totol = todos.length
return (
<div className="todo-footer">
<label>
<input type="checkbox" onChange={this.handleCheckAll} checked={totol === doneTotal && totol !== 0 ? true : false} />
</label>
<span>
<span>已完成 {doneTotal} </span> / 全部 {totol}
</span>
<button onClick={this.handleClearAllDone} className="btn btn-danger">清楚已选择任务</button>
</div>
)
}
}
样式代码:
/*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;
}
【本博文相关代码资料】:我是𝒆𝒅. 的 gitee
再见!