21、react 制作一个 todoList 组件

本文介绍如何使用React构建一个具备添加、删除及标记完成任务功能的TodoList应用。文章详细讲解了组件划分策略,并通过具体代码展示了各部分实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值