listTodo项目二次练习

        通过该项目熟悉,react的事件绑定、父子之间的数据传递,和分模块设计的理念,没有涉及到太多的东西,之前也写过一个todoLIst的项目相比上次知识点用的比较少,但是更加注重细节和规范,显示效果如下: 

        项目结构如下:

        项目划分一个入口,四大组件分别:顶部的输入框、中间的list,list显示的item以及底部的footer,下面分别介绍。

一、package.json

{
  "name": "my-app2",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.3",
    "@testing-library/react": "^12.1.4",
    "@testing-library/user-event": "^13.5.0",
    "nanoid": "^3.3.2",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-scripts": "5.0.0",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
nanoid:用于生成唯一标识的id,相比UUID库更加轻量

二、App

1、App.js

         App.js作为所以组件的父组件,所以将所需要的数组都定义在他里面,以便于子组件之间的相互交互。父组件向子组件传递数据使用props,子组件向父组件传递,使用方法绑定,下面的内容都会涉及到。

import React, {Component} from 'react';
import Header from "./components/header";
import Footer from "./components/footer";
import List from "./components/list";

import './App.css'

export default class App extends Component {

    //默认数据
    state = {
        todoList: [
            {
                id: '001',
                name: '吃饭',
                done: false
            },
            {
                id: '002',
                name: '睡觉',
                done: true
            }
        ]
    }

    render() {
        const {todoList} = this.state
        return (
            <div className={'todo-wrapper'}>
                {/*子组件Header向父组件传递数据,定义addTodo方法,子类this.props.addTodo()即可以调用父类的方法传递*/}
                <Header addTodo={this.addTodo}/>
                <List todoList={todoList} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
                <Footer todoList={todoList} onFooterCheckAll={this.onFooterCheckAll} clearClick={this.clearClickAll}/>
            </div>
        );
    }

    /**
     * 用户添加一个todo添加的对象是一个todo的对象
     * @param todoObj
     */
    addTodo = (todoObj) => {
        //获取源todo
        const {todoList} = this.state
        //追加一个todo
        const newTodo = [todoObj, ...todoList]
        //更新状态
        this.setState({
            todoList: newTodo
        })
    }

    /**
     * 用于更新一个todo
     * @param id
     * @param done
     */
    updateTodo = (id, done) => {
        const {todoList} = this.state
        let list = todoList.map((item) => {
            if (item.id === id) {
                return {...item, done}
            } else {
                return item
            }
        })
        this.setState({
            todoList: list
        })
    }

    /**
     * 删除按钮事件
     */
    deleteTodo = (id) => {
        const {todoList} = this.state
        const list = todoList.filter((item) => {
            return item.id !== id
        })
        this.setState({
            todoList: list
        })
    }

    /**
     * 全部选中
     */
    onFooterCheckAll = (done) => {
        const {todoList} = this.state
        const list = todoList.map(item => {
            return {...item, done}
        })
        this.setState({
            todoList: list
        })
    }

    /**
     * 清空
     */
    clearClickAll = () => {
        const {todoList} = this.state
        const list = todoList.filter(item => {
            return !item.done
        })
        this.setState({
            todoList: list
        })
    }
}

2、App.css

.todo-wrapper {
    margin-left: 30px;
}

ul, li {
    padding: 0;
    margin: 0;
    list-style: none
}

三、Header

1、header/index.js

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

export default class Header extends Component {

    render() {
        return (
            <div className={'header-wrapper'}>
                <input placeholder={'请输入任务名称,按回车键确认'}
                       type={'text'} onKeyDown={this.onKeyDown}/>
            </div>
        );
    }

    /**
     * 监听键盘事件,当为13的时候是回车键
     */
    onKeyDown = (event) => {
        if (event.keyCode === 13) {
            //过滤空数据
            if (event.target.value.trim() === '') {
                return
            }
            //生成对象,调用nanoid()生成ID
            let list = {
                id: nanoid(),
                name: event.target.value,
                done: false
            }
            this.props.addTodo(list)
            //输入框置空
            event.target.value = ''
        }
    }
}

2、header/index.css

.header-wrapper input {
    margin-top: 30px;
    width: 300px;
    height: 32px;
    font-size:14px;
    text-indent: 10px;
}

四、List

1、list/index.js

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

export default class List extends Component {

    constructor(props) {
        super(props);
    }

    render() {
        //不能这么写,错误的
        //const {todoList} = this.props.todoList
        const {todoList, updateTodo, deleteTodo} = this.props
        return (
            <ul>
                {
                    todoList.map(item => {
                        return (
                            //{...item}表示解构数据,即将item的内容挂载在子类Item的props节点上
                            <Item key={item.id} {...item} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
                        )
                    })
                }
            </ul>
        );
    }
}

2、index.css

五、Item

1、item/index.js

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

export default class Item extends Component {

    constructor(props) {
        super(props);
        this.state = {
            mouse: false
        }
    }

    render() {
        //解构数据
        const {mouse} = this.state
        const {id, name, done} = this.props
        return (
            //onMouseEnter、onMouseLeave监听鼠标点击的事件,label表示扩大checkbox的点击范围
            <li className={'item'}
                style={{background: mouse ? '#ddd' : 'white'}}
                onMouseEnter={() => this.onMouseEvent(true)}
                onMouseLeave={() => this.onMouseEvent(false)}>
                <label>
                    <input type={'checkbox'} checked={done}
                           onChange={(event) => this.onCheckChange(event, id)}/>
                    <span>{name}</span>
                </label>
                <button className={'item-button'} style={{display: mouse ? 'block' : 'none'}}
                        onClick={() => this.onDeleteClick(id)}>删除
                </button>
            </li>
        );
    }

    /**
     * 鼠标移动事件
     * @param flag
     */
    onMouseEvent = (flag) => {
        this.setState({
            mouse: flag
        })
    }

    /**
     * 选择框点击时间
     *
     * @param event
     * @param id
     */
    onCheckChange = (event, id) => {
        this.props.updateTodo(id, event.target.checked)
    }

    /**
     * 删除按钮事件
     */
    onDeleteClick = (id) => {
        //注意这里有个window,不写会报错
        if (window.confirm('确定删除吗?')) {
            this.props.deleteTodo(id)
        }
    }
}

2、item/index.css

.item {
    width: 500px;
    height: 100%;
    display: block;
    border: 1px solid gray;
    padding: 5px;
    border-radius: 5px;
    background: white;
    margin: 20px 0 0 0;
}

.item-button {
    float: right;
}

.item :first-child {
    margin-right: 10px;
}

六、Footer

1、footer/index.js

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

export default class Footer extends Component {

    render() {
        const {todoList} = this.props
        //注意reduce的如何使用,怎么过滤数据
        const doneCount = todoList.reduce((previousValue, currentValue) => {
            return previousValue + (currentValue.done ? 1 : 0)
        }, 0)
        const total = todoList.length
        return (
            <div className={'footer-wrapper'}>
                <label>
                    <input type='checkbox' className={'footer-input'}
                           checked={doneCount === total && total !== 0}
                           onChange={this.onFooterCheckAll}/>
                    <span>已完成{doneCount}/全部{total}</span>
                    <button style={{float: 'right'}} onClick={this.clearClickAll}>清除已完成任务</button>
                </label>
            </div>
        );
    }

    /**
     * 调用父类的方法传递数据
     * @param event
     */
    onFooterCheckAll = (event) => {
        this.props.onFooterCheckAll(event.target.checked)
    }

    /**
     * 调用父类的方法传递数据
     * @param event
     */
    clearClickAll = () => {
        this.props.clearClick()
    }
}

2、footer/index.css

.footer-input {
    margin-right: 10px;
}

.footer-wrapper {
    margin-top: 30px;
    width: 500px;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值