0302TodoList案例-react应用

文章目录

    • 1 效果
    • 2 功能拆分和静态组件
    • 3 动态初始化
    • 4 功能实现
      • 4.1 添加todo
      • 4.2 鼠标移入效果和删除todo
      • 4.3 todo选中和取消选中
      • 4.4 底部统计和删除已完成
    • 5 TodoList案例总结
    • 结语

1 效果

通过前面学习React基础和create-react-app脚手架,下面我们做一个经典的入门案例TodoList。效果如下图1-1所示:

0302TodoList案例-react应用_第1张图片

2 功能拆分和静态组件

首先对页面进行拆分,根据功能相关拆分如下图2-1所示组件:

0302TodoList案例-react应用_第2张图片

拆分为4个组件如下:

  • Header组件:对应输入框部分
  • List:对应待办事项列表
    • Item:对应每一个待办事项
      • 包括复选框,文本,鼠标悬浮效果,删除操作等
  • Footer:底部,包括复选框,因完成和总数统计即清楚已完成功能

静态页面如下:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>React App</title>

  <link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
  <div class="todo-container">
    <div class="todo-wrap">
      <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
      </div>
      <ul class="todo-main">
        <li>
          <label>
            <input type="checkbox"/>
            <span>xxxxx</span>
          </label>
          <button class="btn btn-danger" style="display:none">删除</button>
        </li>
        <li>
          <label>
            <input type="checkbox"/>
            <span>yyyy</span>
          </label>
          <button class="btn btn-danger" style="display:none">删除</button>
        </li>
      </ul>
      <div class="todo-footer">
        <label>
          <input type="checkbox"/>
        </label>
        <span>
          <span>已完成0</span> / 全部2
        </span>
        <button class="btn btn-danger">清除已完成任务</button>
      </div>
    </div>
  </div>
</div>

</body>
</html>

静态样式如下:

/*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*/
.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);
}

/*main*/
.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*/
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*/
.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;
}

  • 静态组件

create-react-app脚手架初始化项目,把不需要的去除,建立相应的组件,项目文件结构如下图3-1所示:

0302TodoList案例-react应用_第3张图片

静态组件构建步骤:

  • 把div#root之下的所有结构放入App组件
  • 修改类名和style
    • class修改为className
    • style=“”,修改为jsx语法
  • 把样式放入App.css,查看页面效果。
  • 拆分对应的4个组件和样式,在App中引入对应3个子组件,List组件引入Item子组件。

App.jsx代码2-1如下图所示,其他组件不做展示:

// 创建外壳组件App
import { Component } from 'react'
import Header from './components/Header';
import List from './components/List';
import Footer from './components/Footer';
import './App.css'

class App extends Component {
 
  render() {
    const { todos } = this.state
    return (
      
) } } export default App

3 动态初始化

首先我们来看下该应用场景下数据如何传递,如下图3-1所示:

0302TodoList案例-react应用_第4张图片

我们的数据todos待办事项列表为同一份数据,而这三个组件给兄弟组件,想要进行数据交互,目前为止我们可以把该数据放置在它们共同的父组件App的状态中。

App.jsx组件state初始化代码3-1如下所示:

  /**
   * 待办事项列表
   */
  state = {
    todos: [
      { id: '001', name: '晨练', done: false },
      { id: '002', name: '学习spring', done: true },
      { id: '003', name: '逛街', done: true },
      { id: '004', name: '晚饭', done: false },
    ]
  }

第一步在List中展示待办事项,通过App组件props传递代码如下3-2所示:

  render() {
    const { todos } = this.state
    return (
      
) }

第二步 List中循环展示Item,List.jsx代码3-3如下所示:

import React, { Component } from 'react'
import Item from '../Item';
import './index.css'

export default class List extends Component {
  render() {
    const {todos, updateTodo, deleteTodo} = this.props
    return (
      
    { todos.map(todo => { return }) }
) } }

第三部 Item 展示具体的信息,Item.jsx代码3-4如下:

import React, { Component } from 'react'
import './index.css'

export default class Item extends Component {


  render() {
    const { name, done } = this.props
    
    return (
      
  • ) } }

    4 功能实现

    4.1 添加todo

    Header.jsx初始代码如下4.1-1所示:

    import React, { Component } from 'react'
    import './index.css'
    
    export default class Header extends Component {
    
      render() {
        return (
          
    ) } }

    添加Todo待办事项,流程:

    • 输入框输入待办事项名称
    • 敲击键盘回车键,完成Todo添加

    我们在Header组件需要做的事情:

    • 输入框添加change事件监听;
    • change事件监听回调执行接收输入框数据,判断如果是回车键,把数据传入App组件;
    • App根据接收的数据,更新state,驱动页面更新。

    具体实现:

    • 输入框添加change事件回调,代码4.1-2:

      
      
    • 回调函数代码如下4.1-3:

      /**
         * 键盘事件回调
         * @param {object} event 
         * @returns 
         */
        handleKeyUp = (event) => {
          const {keyCode, target} = event
          // 判断输入enter获取值
          if (keyCode !== 13) {
            return
          }
          // 校验数据,非空
          const val = target.value.trim() || target.value
          if (!val) {
            alert('待做事项不能为空!')
            return
          }
          // 子组件给父组件传值,通过函数调用
          // 构建todo对象
          const todo = {id: nanoid(), name: val, done: false}
          this.props.addTodo(todo)
          // 清空输入框
          target.value = ''
        }
      
      • 父子传值,父组件给子组件传值通过props传递;子组件给父组件传值,子组件通过调用调用props接收父组件传递的函数的参数,传递给父组件。
      • 我们直接传递todo对象,待办事项id通过nanoid库生成。
    • App组件接收todo对象,更新state代码4.1-4:

      /**
       * 添加待办事项
       * @param {*} todo 待办事项
       */
      addTodo = (todo) => {
        this.setState({ todos: [todo, ...this.state.todos] })
      }
      

    4.2 鼠标移入效果和删除todo

    鼠标移入直观效果,Item高亮同时显示删除按钮,可以删除当前Item项。

    流程如下:

    • 鼠标移入,当前待办事项高亮,同时显示删除按钮;
    • 点击删除,给出提示;点击”确定“,删除当前待办事项,取消啥也不做。

    Item组件需要操作:

    • li标签样式根据鼠标移入状态改变;
    • li标签添加鼠标移入事件和鼠标移出事件,同一个回调函数;
    • 通过回调函数接收的参数修改鼠标状态;
    • 删除按钮监听鼠标点击事件;
    • 回调函数获取todo的id向上传递父组件List,List组件向上传递App组件。App组件根据接收的todo的id删除相应的todos列表中的todo对象。

    具体实现:

    • Item.jsx代码4.2-1:

      import React, { Component } from 'react'
      import './index.css'
      
      export default class Item extends Component {
      
        /**
         * 鼠标是否悬浮
         */
        state = { 
          mouse: false // 鼠标进入true;鼠标离开false
        }
      
        /**
         * 鼠标进入离开标志
         * @param {boolean} flag 鼠标进入true;鼠标离开false
         */
        handleMouse = (flag) => {
          return () => {
            this.setState({ mouse: flag })
          }
        }
      
        /**
         * 处理复选框选中状态
         *  状态改变,获取id和状态值向上传递
         * @param {strig} id 
         * @returns 回调函数
         */
        handleCheck = (id) => {
          return (e) => {
            // 获取checkbox选中状态
            const done = e.target.checked
            // id,done向上传递
            const {updateTodo} = this.props
            updateTodo(id, done)
          }
        }
      
        /**
         * 删除一个todu回调
         * @param {string} id 
         */
        handleDelete = (id) => {
          // 校验
          // 原生方法需要指定window前缀
          if (window.confirm("您确定要删除吗?")) {
            // 向上传递id
            this.props.deleteTodo(id)
          }
        }
      
        render() {
          const { id, name, done } = this.props
          const { mouse } = this.state
          return (
            
    • ) } }
    • List组件代码4.2-2:

        { todos.map(todo => { return }) }
    • App组件代码4.2-3:

        /**
         * 根据id删除todo
         * @param {string} id 
         */
        deleteTodo = (id) => {
          // 获取todo列表
          const { todos } = this.state
          // 设置todo对象的done 为true
          const newTodos = todos.filter((todo) => {
            return todo.id !== id
          })
          // 更新状态
          this.setState({ todos: newTodos })
        }
        
      

    4.3 todo选中和取消选中

    效果:

    • 点击待办事项复选框选中;再次点击取消选中。

    组件操作:

    • Item组件复选框监听change事件;
    • 回调函数向上传递todo对象id,done属性;
    • App组件更加传递的id修改相应todo对象的done属性。

    具体实现:

    • Item组件代码4.3-1:

      /**
         * 处理复选框选中状态
         *  状态改变,获取id和状态值向上传递
         * @param {strig} id 
         * @returns 回调函数
         */
        handleCheck = (id) => {
          return (e) => {
            // 获取checkbox选中状态
            const done = e.target.checked
            // id,done向上传递
            const {updateTodo} = this.props
            updateTodo(id, done)
          }
        }
        
      
      • 这里我们输入框属性由defaultChecked改回checked,想想为什么?
    • List组件代码4.3-2:

        { todos.map(todo => { return }) }
    • App组件代码4.3-3:

        /**
         * 更新待办事项
         * @param {string} id 待办事项唯一标识
         * @param {boolean} done 待办事项是否已完成
         */
        updateTodo = (id, done) => {
          console.log(id, '==', done);
          // 获取待办事项列表
          const { todos } = this.state
          // 根据id查找待办事项,修改是否已完成
          const newTodos = todos.map(todo => {
            // 根据id判断是否目标待办事项
            if (todo.id === id) {
              return { ...todo, done }
            } else {
              return todo
            }
          })
          // 更新状态
          this.setState({ todos: newTodos })
        }
        
      

    4.4 底部统计和删除已完成

    效果:

    • 底部显示已完成待办事项数量和总数量,更加上面选中或者取消实时改变;
    • 当上面全部选中后,下面复选框也选中;否则不选中;
    • 点击复选框,全选;再次点击全取消选中;
    • 点击删除已完成,删除已完成待办事项;

    组件操作:

    • 已完成通过统计todos列表中done为true的项;总数量为数组长度;
    • 复选框属性checked初始值通过判断已完成数量和总数量算法相等;复选框监听change事件,回调函数向上传递复选框的值;
    • 复选框全选,遍历todos,修改done为true;复选框全不选,遍历todos,修改每个todo的done值为false;
    • 删除按钮监听鼠标点击事件,回调函数向上传递;

    具体实现:

    • Footer组件代码4.4-1:

      import React, { Component } from 'react'
      import './index.css'
      
      export default class Footer extends Component {
      
        /**
         * 选中全部复选框回调
         */
        handleAllCheck = (event) => {
          this.props.checkAllTodo(event.target.checked)
        }
      
        /**
         * 清除所有已完成回调
         */
        handleAllDone = () => {
          this.props.deleteAllDone()
        }
      
        render() {
          const { todos } = this.props
          // 1 已完成总数
          const doneCount = todos.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
          // 2 总数
          const total = todos.length
          return (
            
      已完成{doneCount} / 全部{total}
      ) } }
    • App组件代码4.4-2:

        /**
         * 选中全部复选框
         */
        checkAllTodo = (done) => {
          // 获取todo列表
          const { todos } = this.state
          // 
          const newTodos = todos.map((todo) => {
            return { ...todo, done }
          })
          // 更新状态
          this.setState({ todos: newTodos })
        }
        /**
         * 删除已完成
         */
        deleteAllDone = () => {
          // 获取todo列表
          const {todos} = this.state
          // 设置todo对象的done 为true
          const newTodos = todos.filter((todo) => {
            return !todo.done
          })
          // 更新状态
          this.setState({todos: newTodos})
        }
        

    5 TodoList案例总结

    • 拆分组件和实现静态组件
      • 注意className和style些饭
    • 动态初始化列表,如何确定数据放在那些组件的state中?
      • 某个组件使用,放在其自身的state中
      • 某些组件使用,放在其共同的父组件中
    • 父子组件通信
      • 父组件给子组件传值:通过props传递
      • 子组件给父组件传值:父组件通过props给子组件传递一个函数,子组件执行该函数通过参数传递。
    • 注意defaultChecked和checked区别
      • defaultChecked:初始状态,初始生效;
      • checked:非初始状态,如需修改,需要通过监听change事件修改。
    • 状态在哪里,操作状态的操作放置在哪里。

    关于对传递数据必要性和类型限制这里不在详述,完整的代码参考底部代码仓库。

    结语

    ❓QQ:806797785

    ⭐️源代码仓库地址:https://github.com/gaogzhen/react-staging.git

    参考:

    [1]React视频教程[CP/OL].2020-12-15.p56-64.

    [2]React官网[CP/OL].

    [2]ChatGPT[CP/OL].

    你可能感兴趣的:(#,React,react.js,前端,javascript)