代码编织梦想

1 效果

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

在这里插入图片描述

2 功能拆分和静态组件

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

在这里插入图片描述

拆分为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所示:

在这里插入图片描述

静态组件构建步骤:

  • 把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 (
      <div className="todo-container">
        <div className="todo-wrap">
          <Header />
          <List />
          <Footer />
        </div>
      </div>
    )
  }

}

export default App

3 动态初始化

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

在这里插入图片描述

我们的数据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 (
      <div className="todo-container">
        <div className="todo-wrap">
          <Header />
          <List todos={todos}/>
          <Footer />
        </div>
      </div>
    )
  }

第二步 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 (
      <ul className="todo-main">
        { 
          todos.map(todo => {
           return <Item {...todo} key={todo.id}/>
          })
        }
      </ul>
    )
  }
}

第三部 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 (
      <li>
        <label>
          <input type="checkbox" defaultChecked={done}/>
          <span>{name}</span>
        </label>
        <button className="btn btn-danger" style={{ display: 'none' }}>删除</button>
      </li>
    )
  }
}

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 (
      <div className="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" />
      </div>
    )
  }
}

添加Todo待办事项,流程:

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

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

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

具体实现:

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

    <input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.handleKeyUp} />
    
  • 回调函数代码如下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] })
    }
    <Header addTodo={this.addTodo} />
    

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 (
          <li style={{ backgroundColor: mouse ? '#ddd' : '#fff' }} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
            <label>
              <input type="checkbox" checked={done} onChange={this.handleCheck(id)}/>
              <span>{name}</span>
            </label>
            <button className="btn btn-danger" style={{ display: mouse ? 'block' : 'none' }} onClick={() => this.handleDelete(id)}>删除</button>
          </li>
        )
      }
    }
    
    
  • List组件代码4.2-2:

    <ul className="todo-main">
      { 
        todos.map(todo => {
         return <Item {...todo} key={todo.id} deleteTodo={deleteTodo}/>
        })
      }
    </ul>
    
  • 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 })
      }
      <List todos={todos} deleteTodo={this.deleteTodo} />
    

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)
        }
      }
      <input type="checkbox" checked={done} onChange={this.handleCheck(id)}/>
    
    • 这里我们输入框属性由defaultChecked改回checked,想想为什么?
  • List组件代码4.3-2:

    <ul className="todo-main">
      { 
        todos.map(todo => {
         return <Item {...todo} key={todo.id} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
        })
      }
    </ul>
    
  • 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 })
      }
      <List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
    

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 (
          <div className="todo-footer">
            <label>
              <input type="checkbox" onChange={this.handleAllCheck} checked={doneCount === total && total !== 0} />
            </label>
            <span>
              <span>已完成{doneCount}</span> / 全部{total}
            </span>
            <button className="btn btn-danger" onClick={this.handleAllDone}>清除已完成任务</button>
          </div>
        )
      }
    }
    
  • 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})
      }
      <Footer todos={todos} checkAllTodo={this.checkAllTodo} deleteAllDone={this.deleteAllDone}/>
    

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].

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/gaogzhen/article/details/130032578

typescript快速入门_ts在线执行-爱代码爱编程

文章目录 TypeScript简介TypeScript安装第一行TypeScript代码 TypeScript的主要特点编译和擦除类型类型推断定义类型联合类型泛型泛型函数:泛型类: 接口描述对象的