React _ 通过 class 类组件 实现前端基础应用 todolist

class 类组件版的 todolist

前言

TodoList 案例在前端学习中挺重要的,从原生 JavaScript 的增删查改,到现在 React 的组件通信,都是一个不错的案例,这篇文章主要还原一下通过 React 实现 TodoList 的全部实际业务需求(增删改查)的实现步骤及组件通信等内容。

拆分组件

首先第一步需要做的是将这个页面拆分成几个组件

  • 首先顶部的输入框,可以完成添加项目的功能,可以拆分成一个 Header 组件

  • 中间部分可以实现一个渲染列表的功能,可以拆分成一个 List 组件

  • 在这部分里面,每一个待办事项都可以拆分成一个 Item 组件

  • 最后底部显示当前完成状态的部分,可以拆分成一个 Footer 组件

项目目录

 

todolist
├─ package.json
├─ public
│ ├─ favicon.ico
│ └─ index.html
├─ src
│ ├─ App.css
│ ├─ App.jsx
│ ├─ Components
│ │ ├─ Footer
│ │ │ ├─ index.css
│ │ │ └─ index.jsx
│ │ ├─ Header
│ │ │ ├─ index.css
│ │ │ └─ index.jsx
│ │ ├─ item
│ │ │ ├─ index.css
│ │ │ └─ index.jsx
│ │ └─ List
│ │     ├─ index.css
│ │     └─ index.jsx
│ └─ index.js
└─ yarn.lock

 

实现动态组件

  1. 动态展示列表 我们目前实现的列表项是固定的,我们需要它通过状态来维护,而不是通过组件标签来维护 首先我们知道,父子之间传递参数,可以通过 stateprops 实现 我们通过在父组件也就是 App.jsx 中设置状态

        //初始化状态
        state = {
            todos:[
                {id:'001',name:'吃饭',done:true},
                {id:'002',name:'睡觉',done:true},
                {id:'003',name:'打代码',done:false},
                {id:'004',name:'逛街',done:false}
            ]
        }
    

      

    我们将 state 传递给 list 组件

    <List todos={todos}/>
    

      

    List 组件中就能通过 props 来获取到 todos

    我们通过解构取出 todos ,再通过map遍历渲染Item数量

    const { todos } = this.props;
    render() {
        const { todos} = this.props;
        return (
          <ul className="todo-main">
            {todos.map((todo) => {
              return (
                <Item
                  key={todo.id}
                  {...todo}
                />
              );
            })}
          </ul>
        );
      }
    

      

     

同时由于我们的数据渲染最终是在 Item 组件中完成的,所以我们需要将数据传递给 Item 组件 这里有两个注意点

  1. 关于 key 的作用在 diff 算法的文章中已经有讲过了,需要满足唯一性

  2. 这里采用了简写形式 {...todo} ,这使得代码更加简洁,它代表的意思是

id = {todo.id} name = {todo.name} done = {todo.done}

  

Item组件中取出props即可使用

const { id, name, done } = this.props

  

这样我们更改APP.jsx文件中的state就能驱动着Item组件的更新,

每个 Item 就是一个li

包含 input 为 chexkbox 的所选框 + todo 事项 + 删除按钮

  
 return (
      <li
        style={{ backgroundColor: mouse ? "#ddd" : "white" }}
        onMouseEnter={this.handleMouse(true)}
        onMouseLeave={this.handleMouse(false)}
      >
        <label>
          <input
            type="checkbox"
            checked={done}
          />
          <span>{name}</span>
        </label>
        <button
          className="btn btn-danger"
          style={{ display: mouse ? "block" : "none" }}
        >
          删除
        </button>
      </li>
    );

  

添加功能

首先我们需要在 Header 组件中,绑定键盘事件,判断按下的是否为回车,如果为回车,则将当前输入框中的内容传递给 APP 组件

因为,在目前的学习知识中,Header 组件和渲染组件 List 属于兄弟组件,没有办法进行直接的数据传递,因此可以将数据传递给 APP 再由 APP 转发给 List。

// Header/index.jsx
handleKeyUp = (event) => {
  // 结构赋值获取 keyCode,target
  const { keyCode, target } = event
  // 判断是不是回车
  if (keyCode !== 13) return
  if(target.value.trim() === '') {
    alert('输入不能为空')
  }
  // 准备一个todo对象
  const todoObj = { id: nanoid(), name: target.value, done: false }
  // 传递给 app
  this.props.addTodo(todoObj)
  // 清空
  target.value = ''
}

  

Header 怎么传递给 App组件 ,App组件然后怎么传递 List 组件呢

// App 组件中 <Header> 标签传递给List 组件一个方法 addTodo
<Header addTodo={this.addTodo}/>
    
//  Header 组件中 就可以使用 props 传递过来的  addTodo方法 将数据 todoObj 以参数形式传递给父组件
this.props.addTodo(todoObj);
​
//  父组件 App 组件就可以 拿到传递过来的  todoObj
//addTodo用于添加一个todo,接收的参数是 todo 对象
addTodo = (todoObj)=>{
    // 获取原 todos
    const {todos} = this.state
    //追加一个todo
    const newTodos = [todoObj,...todos]
    //更新状态
    this.setState({todos:newTodos})
}

  

实现鼠标悬浮效果

首先是鼠标移入时的变色效果 我的逻辑是,通过一个状态来维护是否鼠标移入,比如用一个 mouse 变量,值给 false 当鼠标移入时,重新设定状态为 true 当鼠标移出时设为 false ,然后我们只需要在 style 中用mouse 去设定样式即可

state = { mouse: false } // 标识鼠标移入,移出

  

给元素绑定上鼠标移入,移出事件

<li 
    onMouseEnter={this.handleMouse(true)} 
    onMouseLeave={this.handleMouse(false)} >
<li/>

  

当鼠标移入时,会触发onMouseEnter事件,调用handleMouse事件传入参数true表示鼠标进入,更新组件状态

handleMouse = flag => {
    return () => {
        this.setState({ mouse: flag })
    }
}

  

再在li身上添加由mouse控制的背景颜色

style={{ backgroundColor: this.state.mouse ? '#ddd' : 'white' }}

  

同时通过mouse来控制删除按钮的显示和隐藏,做法和上面一样

<button
     className="btn btn-danger"
     style={{ display: mouse ? "block" : "none" }}
    >
     删除
</button>

  

复选框状态维护

我们需要将当前复选框的状态,维护到 state 当中 我们的思路是 在复选框中添加一个 onChange 事件来进行数据的传递,当事件触发时我们执行 handleCheck 函数,这个函数可以向 App 组件中传递参数,这样再在 App 中改变状态即可 首先绑定事件

// Item/index.jsx
<input 
    type="checkbox" 
    defaultChecked={done} 
    onChange={this.handleCheck(id)} 
    />

  

事件回调 就是调用父组件的方法 updateTodo , 并将 id 和 复选框的值 event.target.checked 传递给父组件的父组件 App

handleCheck = (id) => {
    return (event) => {
        this.props.updateTodo(id, event.target.checked)
    }
}

  

父组件 List 再传递给 App 组件

<Item
     key={todo.id}
     {...todo}
     updateTodo={updateTodo}
     />

  

然后 App 组件中

<List
    todos={todos}
    updateTodo={this.updateTodo}/>

  

父组件中 拿取 传递过来的数据 进行状态更新及页面 展示

//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})
}

  

这里更改的方式是 { ...todoObj, done },首先会展开 todoObj 的每一项,再对 done 属性做覆盖

删除

Item 组件上的按钮绑定点击事件,然后传入被点击事项的 id 值,通过 props 将它传递给父元素 List ,再通过在 List 中绑定一个 App 组件中的删除回调,将 id 传递给 App 来改变 state 首先我们先编写 点击事件 调用 props 传递的事件 deleteTodo 将 id 传递给父组件 List

// Item/index.jsx
handleDelete = (id) => {
    this.props.deleteTodo(id)
}

  

删除按钮添加事件

<button
  onClick={() => this.handleDelete(id)}
  className="btn btn-danger"
  style={{ display: mouse ? "block" : "none" }}
  >
    删除
</button>

 

List 组件

<Item
      key={todo.id}
      {...todo}
      updateTodo={updateTodo}
      deleteTodo={deleteTodo}
      />

 

App 组件 给子组件传递 事件 deleteTodo

<List
    todos={todos}
    updateTodo={this.updateTodo}
    deleteTodo={this.deleteTodo}/>

 

App 组件 deleteTodo 拿取 List 组件传递过来的数据 id 进行删除 及展示


//deleteTodo用于删除一个todo对象
deleteTodo = (id)=>{
    //获取原来的todos
    const {todos} = this.state
    //删除指定id的todo对象
    const newTodos = todos.filter((todoObj)=>{
        return todoObj.id !== id
    })
    //更新状态
    this.setState({todos:newTodos})
}

 

 

获取已完成数量

我们在 App 中向 Footer 组件传递 todos 数据,再去统计数据

<Footer
    todos={todos}
    />

 

Footer 组件中解构todos

const { todos } = this.props;

 

统计 donetrue 的个数

//已完成的个数
const doneCount = todos.reduce((pre, todo) => {
    return pre + (todo.done ? 1 : 0)
}, 0)
//总数
const total = todos.length;

 

再渲染数据即可

<span>
   <span>已完成{doneCount}</span> / 全部{total}
</span>

 

全选

首先我们需要在按钮上绑定事件,由于子组件需要改变父组件的状态,所以我们的操作和之前的一样,先绑定事件,再在 App 中传一个函数个 Footer ,再在 Footer 中调用这个函数并传入参数即可

这里需要特别注意的是 defaulChecked 只有第一次会起作用,所以我们需要将前面写的改成 checked 添加 onChange 事件即可 首先我们先在 App 中给 Footer 传入一个函数 checkAllTodo

//App 组件
 <Footer
     todos={todos}
     checkAllTodo={this.checkAllTodo}
 />

 

事件 checkAllTodo 将所有的父组件传递过来的数据 done 全部更新

//checkAllTodo用于全选
checkAllTodo = (done)=>{
    //获取原来的todos
    const {todos} = this.state
    //加工数据
    const newTodos = todos.map((todoObj)=>{
        return {...todoObj,done}
    })
    //更新状态
    this.setState({todos:newTodos})
}

 

Footer 组件 需要排除总数为0 时的干扰

<input
     type="checkbox"
     onChange={this.handleCheckAll}
     checked={doneCount === total && total !== 0 ? true : false}
     />

 

事件 handleCheckAl 执行父组件传递过来的方法,并传参

 //全选checkbox的回调
  handleCheckAll = (event) => {
    this.props.checkAllTodo(event.target.checked);
  };

 

删除全部

首先在 Footer 组件中调用传来的函数,在 App 中定义函数,过滤掉 donetrue 的,再更新状态即可

// App.jsx
clearAllDone = () => {
  const { todos } = this.state
  const newTodos = todos.filter((todoObj) => {
    return todoObj.done !== true
  })
  this.setState({ todos: newTodos })
}

 

App 组件中 传递 事件 给子组件 Footer

<Footer
    todos={todos}
    checkAllTodo={this.checkAllTodo}
    clearAllDone={this.clearAllDone}
    />

 

Footer 组件 绑定 删除所有 按钮的事件handleClearAllDone

<button
    onClick={this.handleClearAllDone}
    className="btn btn-danger">
    清除已完成任务
</button>

 

Footer 组件调用 props 传递过来的事件clearAllDone

  
//清除已完成任务的回调
  handleClearAllDone = () => {
    this.props.clearAllDone();
  };

 

 
posted @ 2023-08-22 00:08  playforkeeps  阅读(68)  评论(0)    收藏  举报