二、【React脚手架】组件化编码(TodoList案例)

本文档详细介绍了React组件化的编码流程,包括拆分组件、实现静态和动态组件,以及处理数据交互。同时,针对样式覆盖问题提出了使用Less的嵌套规则和.module.css方法。此外,还提供了一个DEMO,展示了如何创建一个待办事项列表应用,涵盖了添加、删除、切换状态等功能,并使用prop-types库限制props类型,以及使用nanoid生成唯一ID。
摘要由CSDN通过智能技术生成

1、组件化编码流程(通用)

  1. 拆分组件: 拆分界面,抽取组件

  2. 实现静态组件: 使用组件实现静态页面效果

  3. 实现动态组件

    1. 动态显示初始化数据
      1. 数据类型
      2. 数据名称
      3. 保存在哪个组件?
    2. 交互(从绑定事件监听开始)

2、样式覆盖问题

  1. 使用 less,例如import '.\xxx.less',样式采用嵌套格式,顶级样式名不重即可,建议使用组件名
    1. 也可使用2的方法,此时不必写 .module
  2. css样式文件加 .module,例如 xxx.module.css
    1. 用参数命名引入这个样式文件: import styles from 'xxx.module.css'
    2. 使用时采用react插值方式:className={styles.xx}

3、DEMO

3.1、需要实现的效果

在这里插入图片描述

3.2、前期须知

  • 如果要对 props 进行限制,需要引入 prop-types 库

  • id 需要使用不重复随机数,推荐使用nanoid

    • 安装指令:yarn add nanoid

    • 引入函数:import { nanoid } from 'nanoid'

    • 使用函数:nanoid()

3.3、项目结构

只有红框内的文件有修改或是新增的

在这里插入图片描述

3.4、CODE

3.4.1、App.js

import React, { Component } from 'react'
import { nanoid } from 'nanoid'
import Header from './components/ToDoList/Header'
import List from './components/ToDoList/List'
import Footer from './components/ToDoList/Footer'
import './App.css'

export default class App extends Component {

  state = {
    todoList: [{
      id: '001',
      name: '吃饭',
      done: false
    }, {
      id: '002',
      name: '睡觉',
      done: false
    }, {
      id: '003',
      name: '打豆豆',
      done: false
    }]
  }

  toNever = id => {
    const { todoList } = this.state
    todoList.map(item => {
      if (item.id === id) {
        item.done = !item.done
      }
      return item
    })
    this.setState({ todoList })
  }

  addOne = name => {
    const { todoList } = this.state
    this.setState({
      todoList: [
        {
          id: nanoid(),
          name,
          done: false
        },
        ...todoList
      ]
    })
  }

  deleteById = id => {
    if (!window.confirm('确定删除吗?')) return
    const { todoList } = this.state
    this.setState({ todoList: todoList.filter(item => item.id !== id) })
  }

  deleteDone = () => {
    if (!window.confirm('确定删除吗?')) return
    const { todoList } = this.state
    this.setState({ todoList: todoList.filter(item => !item.done) })
  }

  doneAllOrNot = () => {
    const { todoList } = this.state
    const dones = todoList.filter(i => i.done).length

    todoList.map(item => {
      item.done = !(todoList.length === dones)
      return item
    })
    this.setState({ todoList })
  }

  render() {
    return (
      <div className="todo-container" >
        <div className="todo-wrap">
          <Header addOne={this.addOne} />
          <List {...this.state} toNever={this.toNever} deleteById={this.deleteById} />
          <Footer {...this.state} doneAllOrNot={this.doneAllOrNot} deleteDone={this.deleteDone} />
        </div>
      </div>
    );
  }
}

3.4.2、App.css

/*base*/
body {
    background: #fff;
}

.btn {
    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;
    display: none;
    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;
    display: flex;
    justify-content: space-between;
}

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:hover .btn {
    display: block;
}

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;
}

.todo-footer:hover .btn {
    display: block;
}

3.4.3、Header

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class Header extends Component {

    static propTypes = {
        addOne: PropTypes.func.isRequired
    }

    add = e => {
        const { addOne } = this.props
        if (e.key === 'Enter') {
            if (!!e.target.value.trim()) {
                addOne(e.target.value)
                e.target.value = ''
            } else {
                alert('请输入非空任务名')
            }
        }
    }
    render() {
        return (
            <div className="todo-header">
                <input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.add} />
            </div>
        )
    }
}

3.4.4、List

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

export default class List extends Component {

    render() {
        const { todoList, toNever, deleteById } = this.props
        return (
            <ul className="todo-main">
                {
                    todoList.map(item => <Item key={item.id} todo={item} toNever={toNever} deleteById={deleteById} />)
                }
            </ul>
        )
    }
}

3.4.5、Item

import React, { Component } from 'react'

export default class Item extends Component {
    render() {
        const { id, name, done } = this.props.todo
        const { toNever, deleteById } = this.props
        return (
            <li>
                <label>
                    <input type="checkbox" checked={done} onChange={() => toNever(id)} />
                    <span>{name}</span>
                </label>
                <button className="btn btn-danger" onClick={() => deleteById(id)}>删除</button>
            </li>
        )
    }
}

3.4.6、Footer

import React, { Component } from 'react'

export default class Footer extends Component {
    render() {
        const { todoList, doneAllOrNot, deleteDone } = this.props
        const total = todoList.length
        const done = todoList.filter(item => item.done).length
        return (
            <div className="todo-footer">
                <label>
                    <input type="checkbox" checked={!!(total || 0) && total === done} onChange={doneAllOrNot} />
                </label>
                <span>
                    <span>已完成{done || 0}</span> / 全部{total || 0}
                </span>
                <button className="btn btn-danger" onClick={deleteDone}>清除已完成任务</button>
            </div>
        )
    }
}


小白学习参考视频:尚硅谷React教程

中文官网:React 官方中文文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值