Redux 有多烦人,有多复杂?

181 阅读26分钟

说实话,我是抱着学会React可以实践项目的想法,出发的,现在想想有些后悔了,太难了,太耗时了,真的不想坚持,学习完Class组件,还要高阶,终于摆脱烦人的this指向问题了,又来个复杂的,啰里啰嗦的Redux, 问过很多人, 不就是 state 不能直接改变, 需要通过reducer, 但是只给reducer 一个state 他不知道什么时候,执行什么,改变什么,就用到了action, action 只是知道类型了, 在reducer里操作了,发现没有效果呀,我更改的值呢,奥,需要dispatch 方法改变视图更新了,说到这里头都大了都是什么玩意呀,来现在我们一层层的捋,让你不再放弃他,放弃他你会放弃很多money,也许我说的对吧,我们现在通过几个案例来实现一级级增加难度,你会发现,我这么笨的人都会了,你能不会吗?? 来开始了

第一个案例

我们通过Class组件实现一个todolist+antd,我们的目的是巩固一下类组件的使用和使用中会遇到哪些问题??

Class 编写 todoList开始

1.按照页面结构拆分组件

image.png

渲染组件 Home

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

class Home extends Component {
    return(

        <div></div>
    )
}

头部组件 Header

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

class Header extends Component {
   return(

        <div></div>
    )

}

列表组件 List

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

class List extends Component {
   return(
        <div></div>
    )
}

列表每一项组件 Item

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

class Item extends Component {
    return(
        <div></div>
    )

}

底部组件 Footer

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

class Item extends Component {
   return(

        <div></div>
    )

}

2.添加每个组件的样式

Header 组件-样式

    /*header*/
.todo-header {
    margin: 20px 0px;
}

.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 10px 20px;
}

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

list组件-样式

 /*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组件-样式

/*item*/
.Item-Container {
    list-style: none;
    height: 36px;
    display: flex;
    justify-content: space-between;
    width: 100%;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
}

.Item-Container label {
    float: left;
    cursor: pointer;
}

.Item-Container label .Item-Container input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
}

.Item-Container button {
    float: right;
    display: none;
    margin-top: 3px;
    margin-left: 100px;
}

.Item-Container:before {
    content: initial;
}

.Item-Container:last-child {
    border-bottom: none;
}

Footer组件-样式

   /*footer*/
.todo-footer {
    height: 40px;
    line-height: 40px;
    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;
}

App.css

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

3.编写组件之间的交互逻辑

逻辑部分-----------解析 Home 逻辑编写

import React, {Component} from 'react';
import {Button, Divider, Input,} from "antd";
import List from '../components/List'
import Header from "../components/Header";
import Footer from "../components/Footer";
import  './../App.css'
class Home extends Component {
    // 现在要实现一个todoList  需要 Input 添加,  列表数据, 删除事件, 勾选单个, 
    // 取消勾选, 全部勾选,全部取消, 清除全部任务
    // ----------------------------------------------
    //创建一个list---都可以这两种
    // this.state = {}
    state = {
        InfoData: [
            {
                id: '001',
                title: '怎么了',
                done: false
            },
            {
                id: '002',
                title: '谁呀',
                done: true
            },
            {
                id: '003',
                title: '不认识',
                done: false
            },
        ],
    }
    // 事件的编写:  this.addTaskListHandle.bind(this)   => addTaskListHandle()// 新增数据
    // obj 为子传递父父接收的参数用来添加逻辑
    addTaskListHandle = (obj) => {
        // 获取一下InfoData    addTaskListHandle(){}  // 这样获取不到 state
        const {获取一下InfoData} = this.state
        // 创建一个新的news列表处理逻辑
        const newtodos = [obj, ...InfoData]
        // 更新列表数据
        this.setState({
            InfoData: newtodos
        })
    }

    return(

    // 头部组件
    // 分析: 先处理头部组件,头部组件是一个输入框需要 InfoData ?  不需要对吧, 需要输入后回车 添加对吧? 这个地方需要一个添加事件
    //  现在的问题是 需要摁下回车, 才会添加, 但是 当前Home组件只有添加怎么办呢? 就用到 子组件编写回车逻辑传参给父组件,父组件接收解决
<Header addTaskListHandle= {
    this.addTaskListHandle
}

/> // 这样既可以传递方法给Header组件也可以接收Header 组件传递的值
// 列表组件
<List/>
// 底部组件
<Footer/>
)

}
  1. Header 组件逻辑编写
    import React, {Component} from 'react';
    import PropTypes from "prop-types"; // 数据/事件类型强制判断
    import {nanoid} from "nanoid"; // 主键引入用于删除
    import './index.css'
    import {Input} from "antd";
    
    class Header extends  Component{
        // 对接收的props 进行限制
        static  propTypes ={
            addTaskListHandle:PropTypes.func.isRequired // 添加事件
        }
        // 键盘事件回车事件编写
        handleKeyUp =(event) =>{
            // 解构赋值获取keyCode,target
            const {keyCode, target} = event
            // 判断是否回车按键
            if(keyCode !==13) return
            // 添加的title 名字不能为空判断
            if(target.value.trim() === ""){
                alert("输入不能为空")
                return
            }
            // 数据的添加模式传给Home
            this.props.addTaskListHandle(todoObj)
            //清空输入框
            target.value = ''
        }
        render() {
            return (
                // 头部组件的内部Dom结构
                <div  className="todo-header">
                    <Input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
                </div>
            )
        }
    }

List 组件编写逻辑解析---结合 Home组件

import React, {Component} from 'react';
import {Button, Divider, Input,} from "antd";
import List from '../components/List'
import Header from "../components/Header";
import Footer from "../components/Footer";
import  './../App.css'
class Home extends Component {
    // 现在要实现一个todoList  需要 Input 添加,  列表数据, 删除事件, 勾选单个, 
    // 取消勾选, 全部勾选,全部取消, 清除全部任务
    // ----------------------------------------------
    //创建一个list---都可以这两种
    // this.state = {}
    state = {
        InfoData: [
            {
                id: '001',
                title: '怎么了',
                done: false
            },
            {
                id: '002',
                title: '谁呀',
                done: true
            },
            {
                id: '003',
                title: '不认识',
                done: false
            },
        ],
    }
    // 接收的参数=> 父组件接收的参数
    updateTodo =(id,done) =>{
        // 解构列表的数据
        const {InfoData} = this.state
        const newtodos = InfoData.map((ele) =>{
            // 判断如果id 相等勾选,否则取消勾选
            if(ele.id ===id) return { ...ele,done}
            else  return  ele
        })
        // 更新数据
        this.setState({ InfoData:newtodos})
    }
    return(
        // 列表组件
        <Header/>
        // 列表数据:InfoData
        <List InfoData={InfoData}
        // // 勾选和取消勾选用于更新一个对象
         updateTodo={this.updateTodo}
        //删除
         deleteTaskListHandle={this.deleteTaskListHandle}/>
        // 底部组件
        <Footer/>
    )
}

List 组件逻辑编写

import React, {Component} from 'react';
import {Avatar, List} from "antd";
import './index.css'
import Item from '../Item/Item' // list里面的每一项组件
// 类型判断
import PropTypes from 'prop-types';

class HomeItem extends Component {
    // 约束
    // eslint-disable-next-line react/no-typos
    static  propTypes = {
        InfoData: PropTypes.array.isRequired,
        updateTodo: PropTypes.func.isRequired,
        deleteTaskListHandle: PropTypes.func.isRequired
    }
    render() {
        const {InfoData, updateTodo, deleteTaskListHandle} = this.props // 获取父组件的参数
        return(
            <List
                itemLayout="horizontal"
                className="todo-main"
                dataSource={InfoData} // 数据源
                renderItem={(ele) => (
                    // 每一项item
                    <List.Item>
                        <Item
                            // key 唯一标识
                            key={ele.id}
                            // 扩展所有的数据
                            {...ele}
                            // 取消和选中的事件
                            updateTodo={updateTodo}
                            // 删除某一项
                            deleteTaskListHandle={deleteTaskListHandle}>
                        </Item>
                    </List.Item>
                )}
            />
        )
    }
}

Item 组件里面--逻辑编写

  // 分析Item涉及到的功能, 勾选,取消勾选, 删除, 鼠标移入移除动画效果
import React, {Component} from 'react';
import {Button, Checkbox, Input} from "antd";
import  './index.css'
class Item extends Component {
    // 创建一个移入移除的状态 mouse 
    
    state = {
        mouse:false // 默认为不显示
    }
    // 鼠标移入移出事件的回调
    HandleMouse =(flag) =>{
        return () =>{
            this.setState({mouse:flag})
        }
    }
    // 勾选,取消勾选的回调-- 根据id判断
    checked =(id) =>{
        return (event) =>{
            // 调用 Home 编写的逻辑 => 勾选取消勾选
            this.props.updateTodo(id,event.target.checked)
        }
    }
    deleteTodo =(id) =>{
        return () =>{
            if(window.confirm('确定删除吗?')){
                // 调用 Home 编写的逻辑=> 删除的功能
                this.props.deleteTaskListHandle(id)
            }
        }
    }
    render(){
        const { id,title,done} = this.props // {...ele} // 扩展的组件传参
        const { mouse } = this.state
        return (
            <div className="Item-Container" style={{backgroundColor:mouse?'#ddd':'white'}}
                 {/* 
                 onMouseEnter: 鼠标移入
                 onMouseLeave: 鼠标移除
                 */}
                 onMouseEnter={this.HandleMouse(true)} onMouseLeave={this.HandleMouse(false)} > 
                {/*鼠标移入的时候的显示的样式颜色*/}
                <div>
                    <label>
                        {/* 渲染 多选按钮  checked: 选择的状态, onChange: 改变的状态*/}
                        <Checkbox type="checkbox"
                                  checked={done}
                                  onChange={this.checked(id)}
                        />
                        <span style={{marginLeft:'20px'}}>{title}</span>
                    </label>
                </div>
                <div>
                    {/* 删除的逻辑处理: deleteTodo:根据id删除*/}
                    <button onClick={this.deleteTodo(id)}
                            className="btn btn-danger"
                            {/*   删除按钮的显示隐藏 根据 鼠标的移入或者移除*/}
                            style={{ display: this.state.mouse ? 'block' : 'none' }}
                    >删除</button>
                </div>
            </div>
        )
    }
}

Footer 组件的逻辑编写 -- Home

import React, {Component} from 'react';
import {Button, Divider, Input,} from "antd";
import List from '../components/List'
import Header from "../components/Header";
import Footer from "../components/Footer";
import  './../App.css'
class Home extends Component {
    // 现在要实现一个todoList  需要 Input 添加,  列表数据, 删除事件, 勾选单个, 
    // 取消勾选, 全部勾选,全部取消, 清除全部任务
    // ----------------------------------------------
    //创建一个list---都可以这两种
    // this.state = {}
    state = {
        InfoData: [
            {
                id: '001',
                title: '怎么了',
                done: false
            },
            {
                id: '002',
                title: '谁呀',
                done: true
            },
            {
                id: '003',
                title: '不认识',
                done: false
            },
        ],
    }
    // 全选事件 根据状态全选 done=> 需要子传父=> 接收的参数为e.target.checked = (done) 
    checkListtodo = (done) => {
        const {InfoData} = this.state
        //合并数据
        const list = InfoData.map((ele) => {
            return {...ele, done}
        })
        // 更新数据
        this.setState({
            InfoData: list
        })
    }
    // 清除全选事件
    clearallListchecked = () => {
        // 解构list数据
        const {InfoData} = this.state
        // 根据: !ele.done 过滤 list 数据
        const list = InfoData.filter((ele) => {
            return !ele.done
        })
        // 更新数据
        this.setState({
            InfoData: list
        })
    }
    return(
        // 列表组件
        <Header/>
        // 列表数据:InfoData
        <List/>
        // 底部组件: 实现 全选或者取消全选, 清除勾选的任务(删除) 需要list 数据
        // 全选或者取消全选checkListtodo
        // 清除勾选的任务(删除):clearallListchecked
        <Footer  InfoData={InfoData}     checkListtodo ={this.checkListtodo}  clearallListchecked={this.clearallListchecked}/>
    )
}

Footer 组件部分逻辑编写

import React, {Component} from 'react';
import  './index.css'
class Footer extends Component {
    // 全选
    handleCheckAll = (ele) =>{
        // 子传父参数
        this.props.checkListtodo(ele.target.checked)
    }
    // 取消全选
    handleClearAllDone =(ele) =>{
        this.props.clearallListchecked()
    }
    render() {
        // 解构 Home 组件的InfoData
        const { InfoData} = this.props
        // 完成数量统计 使用reduce((pre,ele) =>{return pre + (ele.done ?1:0)},0)
        // 已完成数个数
        const doneCount = InfoData.reduce((pre,ele) =>{
            return pre + (ele.done ?1:0)
        },0)
        // 总数
        const total = InfoData.length
        return (
            <div className="todo-footer">
                <label>
                    {/* 可以改成 checkbox */}
                    <input type="checkbox"
                           {/* 选中与取消选中的事件  */}
                           onChange={this.handleCheckAll}
                           {/*  根据 总数是否等于0 显示选中*/}
                           checked={doneCount&& total !==0 ? true : false}/>
                </label>
                <span>已完成{doneCount}</span>/全部{total}
                {/* 清空选中的方法*/}
                <button className="btn btn-danger" onClick={this.handleClearAllDone}>清除已完成任务</button>
            </div>
        );
    }
}


export default Footer;

补充全部的Home 组件的内容

import React, {Component} from 'react';
import {Button, Divider, Input,} from "antd";
import List from '../components/List'
import Header from "../components/Header";
import Footer from "../components/Footer";
import  './../App.css'
class Home extends Component {
    state = {
        InfoData: [
            {
                id: '001',
                title: '怎么了',
                done: false
            },
            {
                id: '002',
                title: '谁呀',
                done: true
            },
            {
                id: '003',
                title: '不认识',
                done: false
            },
        ],
        inputVal: ''
    }

    // 输入改变的值
    InputChange(e) {
        console.log(e.target.value)
        this.setState({
            inputVal: e.target.value
        })
    }

    // 新增数据
    addTaskListHandle = (obj) => {
        // 获取原InfoData
        const {InfoData} = this.state
        // 追加一个InfoData
        const newtodos = [obj, ...InfoData]
        this.setState({
            InfoData: newtodos
        })
    }

    // 勾选和取消勾选,用于更新一个对象
    updateTodo = (id, done) => {
        // 获取状态中的todos
        console.log(this.state,"satte")
        const {InfoData} = this.state
        const newtodos = InfoData.map((ele) => {
            if (ele.id === id) return {...ele, done}
            else return ele
        })
        this.setState({InfoData: newtodos})
    }

    // 删除的逻辑
    deleteTaskListHandle = (id)  =>{
        const {InfoData} = this.state
        const newTodos = InfoData.filter((todoObj)=>{
            return todoObj.id !==id
        })

        this.setState({
            InfoData: newTodos
        })
    }

    // 全选事件
    checkListtodo = (done) => {
        const {InfoData} = this.state
        const list = InfoData.map((ele) => {
            return {...ele, done}
        })
        this.setState({
            InfoData: list
        })
    }
    // 清除全选事件
    clearallListchecked = () => {
        const {InfoData} = this.state
        const list = InfoData.filter((ele) => {
            return !ele.done
        })
        this.setState({
            InfoData: list
        })
    }

    // 已完成&& 未完成
    //----------------已删除-------------------------
    // completeTask(title) {
    //     const TodoList = []
    //     this.state.InfoData.forEach((ele, index) => {
    //         if (ele.title === title) {
    //             const item = this.state.InfoData[index]
    //             TodoList.push(Object.assign({}, item, {status: item.status === 0 ? 1 : 0}))
    //             this.setState({
    //                 InputData: TodoList
    //             })
    //         } else {
    //             TodoList.push(ele)
    //         }
    //     })
    // }
    //---------------已删除--------------------------
    render() {
        const {InfoData} = this.state
        return (
            <div className="todo-container">
                <div className="todo-wrap">
                    <Header addTaskListHandle={this.addTaskListHandle}/>
                    <List InfoData={InfoData}
                              updateTodo ={this.updateTodo}
                              deleteTaskListHandle={this.deleteTaskListHandle}
                    />
                    <Footer
                        InfoData={InfoData}
                        checkListtodo ={this.checkListtodo}
                        clearallListchecked={this.clearallListchecked}
                    />
                </div>
                {/*<Input style={{width: '200px', margin: '20px 20px'}}*/}
                {/*       type="text" value={inputVal} onChange={this.InputChange.bind(this)}/>*/}
                {/*<Button onClick={this.addTaskListHandle.bind(this)}>添加</Button>*/}
                {/*<Divider/>*/}
                {/*<HomeItem InfoData={InfoData} deleteTaskListHandle={this.deleteTaskListHandle.bind(this)}/>*/}
            </div>
        );
    }
}

export default Home;

Class编写todoList完结

第二个案例

Hooks 编写todoList 开始 这里不讲Hooks的使用规范了,直接在业务中标注

问题1: 改造hooks 遇到的数据类型问题

const { InfoData,setInfoData} = useState([
    {
        id: '001',
        title: '怎么了',
        done: false
    },
    {
        id: '002',
        title: '谁呀',
        done: true
    },
    {
        id: '003',
        title: '不认识',
        done: false
    },
])

问题2 : 更新数据的使用

 setInfoData({})

问题3: 函数如何编写:

    const dfun1 = () =>{}

问题4: return里函数数据如何编写

<Header addTaskListHandle={() =>addTaskListHandle()}/>
<List InfoData={InfoData}
      updateTodo ={()=>updateTodo()}
      deleteTaskListHandle={()=>deleteTaskListHandle()}
/>
<Footer
    InfoData={InfoData}
    checkListtodo ={() =>checkListtodo()}
    clearallListchecked={() =>clearallListchecked()}
/>

言归正传我们现在来说一下,hooks 如何编写待办事项

整个项目todolist

import React, {createContext, memo, useCallback, useContext, useEffect, useRef, useState} from 'react'
import {Button, Checkbox} from "antd";
import  './App.css'
// eslint-disable-next-line react-hooks/rules-of-hooks
// 出错原因:  创建中间件 使用了useContext
const TodoContext = createContext(0)
// Header 组件
/*
* 对象中的属性分别是:
    id 事务的唯一标识符;
    haveDone 该事务是否已完成(默认是未完成);
    name 该事务的名称;
* **/
function Header(props) {
    // 注意这里接收的props,是 addItem 方法
    const {addItem} = props
    // useRef 用来获取元素
    const ipt = useRef(null);
    // 点击提交的事务
    const handleAddItem = (e) => {
        // 这里是为了阻止默认事件触发
        // form 表单提交会默认跳转页面
        e.preventDefault();
        // 判断 输入框里是否有值
        if (!ipt.current.value) {
            // 如果提交时发现input 框没值, 则什么都不做
            return
        }
        // 不然就添加事务
        addItem({
            name: ipt.current.value,
            haveDone: false,
            id: +new Date()
        })
        // 添加完后清空输入框
        ipt.current.value = ""
    }
    return (
        <form className="todo-form">
            {/* 我们通过ref 获取  value的值*/}
            <input ref={ipt} type="text" placeholder="请输入待办事务"/>
            <Button onClick={handleAddItem} type="primary">
                submit
            </Button>
        </form>
    )

}

const  List = memo((props) =>{
    const context = useContext(TodoContext)
    // context 是个对象
    /**
     * 左边复选框为了改变状态,中间是事务名称,右边是删除事务按钮。
     * 因此List组件需要传入 toggle、removeItem、itemList 三个 props。
     * */
    const {infoList, remove, toggle} = props;
    // 修改前
    // return (
    //     <ul className="listWrapper">
    //         {
    //             //这里渲染的每一项事务
    //             infoList.length ? infoList.map(item => {
    //                 return (
    //                     <Item
    //                         key={item.id}
    //                         remove={remove}
    //                         toggle={toggle}
    //                         info={item}
    //                     />
    //                 )
    //             }) : ''
    //         }
    //     </ul>
    // )
    // 修改后
    return (
        <ul className="listWrapper">
            {
                //这里渲染的每一项事务
                context.itemList.length === 0 ? "":
                    context.itemList.map(item =>{
                        return (
                            <Item
                                key={item.id}
                                info={item}
                            />
                        )
                    })

            }
        </ul>
    )
})

const Item = (props) => {
    const context = useContext(TodoContext)
    // const {remove, toggle, info} = props
    const info = props.info;

    // 切换事务状态
    const handleChange = () => {
        // 根据 info =>id移除
        context.toggle(info.id)
    }
    // 移除事务
    const handleRemove = () => {
        context.removeItem(info.id)
    }
    return (
        <li className="todo-item">
            {/* 没有进行标注是因为单词写错了haneDone =>haveDone*/}
            <Checkbox
                onChange={() =>handleChange()}
                checked={info.haveDone}
            />
            {/* 根据状态切换*/}
            <label
                className={info.haveDone ? "routine" : ""}
            >{info.name}</label>
            <span onClick={handleRemove} className="remove">
                &#215;
            </span>
        </li>
    )
}
// 底部组件 footer

const Footer = () =>{
    const context = useContext(TodoContext)
    return(
        <div id="footer">
            <div id="footer-left">
                {/*
                  判断是否全选的 当已选中的列表的length===列表的length 并且列表数据不是空的就是全选状态
                */}
                <input
                    type="checkbox"
                    onChange={context.checkListtodo}
                    checked={context.finishCount === context.itemList.length && context.itemList.length > 0}
                />
                <span>全选/取消全选</span>
            </div>
            <div id="footer-right">
                <div>已完成{context.finishCount}项/总计{context.itemList.length}件</div>
                <div>
                    <Button onClick={context.clearallListchecked}>删除已完成</Button>
                </div>

            </div>
        </div>
    )
}
function App() {
    // 防止刷新后,消失
    const NOTES = 'notes';
    // 定义一个 useContent
    const [itemList, setitemList] = useState([])
    const [finishCount,setFinishCount] = useState(0) // 勾选的个数
    const [slectedAll,setSlectedAll] = useState(false)// 是否全选
    // 定义state 初始化为一个空数组, 用以存放事务的数据
    // 添加事务 如果括号里有形参表示会涉及到 根据类型状态子传父
    //为了不它的参数(一个函数)做不必要的重复调用,这个函数是个纯函数,使用


    //函数是为了从本地存储中找数据,找数据定是在组件挂载之后,即 componentDidMount 函数中调用。
    // 因此useEffect第二个参数是个空数组。
    useEffect(() => {
        let data = JSON.parse(localStorage.getItem(NOTES));
        setitemList(data);
    },[]);
    // useEffect 函数是设置新的存储数据在 itemList 的值变化后就会执行。
    useEffect(() => {
        localStorage.setItem(NOTES,JSON.stringify(itemList));
    },[itemList]);


    // useCallback
    /**
     * 就是当输入相同的内容时,List 不应该渲染相同的数据项,
     * 因此需要去重,只需在 addItem 重新写一些去重即可:
     * */
    const addItem = useCallback((info) => {
        // 这里使用扩展运算符,实现浅拷贝
        // 因为 state 不能直接做更改
        // find 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
        if(itemList.find((ele) =>ele.name === info.name)){
            return;
        }
        setitemList([...itemList, info])
    }, [itemList]) // 对当前值的更改变化的话会调用
    // 删除事务
    // 根据 ID 移除事务
    // 这里使用数组的 filter 方法,
    // 根据ID的不同过滤掉要移除的事务
    // 值得注意的是,filter 方法返回的是一个新的数组,
    // 原数组并没有改变
    // 如果括号里有形参表示会涉及到 根据类型状态子传父
    const removeItem = useCallback((id) => {
        setitemList(itemList.filter(value => value.id !== id));
    }, [itemList])
    // 更改任务的状态
    // 根据 ID 变换事务的完成状态
    // 这里使用 map 在map前,使用concat方法实现数组拷贝
    // 如果括号里有形参表示会涉及到 根据类型状态子传父
    const toggle = useCallback((id) => {
        // 根据 ID 变换事务的完成状态
        // 这里使用 map 在map前,使用concat方法实现数组拷贝
        setitemList([].concat(itemList).map((item) => {
            // 根据id判断
            // 点击 checkbox 复选框时,完成变成未完成
            // 未完成可变成完成状态
            // 改成haveDone 就可以标注了
            if (item.id === id) {
                item.haveDone = !item.haveDone
            }
            // 返回最新item
            return item
        }))
    }, [itemList])
    // 全选列表逻辑
    const checkListtodo = (id) =>{
        itemList.forEach((todo) => todo.haveDone = !slectedAll);
        setitemList(itemList);
        setSlectedAll(!slectedAll);
        if(itemList.length === 0){
            alert("现在还没有任务了,请先添加任务!")
        }
        const newTodos = itemList.filter((todo) => todo.haveDone === true);
        setFinishCount(newTodos.length);
    }
    // 清除选择的任务在列表中
    const clearallListchecked = () =>{
        const newTodos = itemList.filter((todo) => todo.haveDone === false);
        console.log(newTodos,"+++++++++++++++++====>")
        setitemList(newTodos);

        const newTodosFnished = newTodos.filter((todo) => todo.haveDone === true);
        setFinishCount(newTodosFnished.length);

        const isFinished = itemList.filter((todo) => todo.haveDone === true);
        // 当前删除的值是我勾选后,做的逻辑处理
        console.log(isFinished,"=========>>>>")
        if(isFinished.length === 0){
            alert("现在还没有已经完成的任务哦😯!")
        }
    }
    /**
     * 这三个 useCallback 的第三个参数都是 itemList 是因为,
     * 在useCallback函数中,state有且正好只有 itemList ,
     * 即:只有 itemList 发生改变后页面才做渲染。itemList 是个数组,
     * 它的每一项则是个对象,每个对象对应一个事务的状态
     * */
    // 修改前
    // return (
    //     <div className="wrapper">
    //         <h1>todoList</h1>
    //         {/*Header 用来盛放输入以及 提交按钮 */}
    //         {/* 添加功能*/}
    //         <Header addItem={addItem}/>
    //         {/*   List 用来盛放任务列表   */}
    //         {/* 传入渲染的list*/}
    //         <List infoList={itemList}
    //               remove={removeItem}
    //               toggle={toggle}/>
    //     </div>
    // )
    // 修改后
    // 还有一个 Hook 没有用到,那就是 useContext 该方法配合 context 使用,
    // 使得无需为每层组件手动添加 props,就能在组件树间进行数据传递。
    //很明显,在 todoList 程序中,
    // TodoList 组件传递的部分 props 经过了两次才传递给 最里层 Item 组件

    return(
        <div className="wrapper">
            <h1 className="title">todoList</h1>
            {/*Header 用来盛放输入以及 提交按钮 */}
            {/* 添加功能*/}
            <Header addItem={addItem}/>
            {/*   List 用来盛放任务列表   */}
            {/* 传入渲染的list*/}
            {/* value 部分使用了对象的简写语法 */}
            {/*消费者*Provider*/}
            <TodoContext.Provider value={{
                itemList,removeItem,toggle,finishCount,
                checkListtodo,clearallListchecked
            }}>
                {/* 要订阅的组件*/}
                <List/>
                <Footer/>
            </TodoContext.Provider>
        </div>
    )
}
export default App

整个样式文件App.css

*{
  padding: 0;
  margin: 0;
}
.wrapper{
  width: 60%;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 100px auto;
  text-align: center;
  background-color: rgba(141, 139, 139, 0.1);
  border-radius: 20px;
  padding: 10px 30px 0px 30px;
  border: 1px solid #ccc;
}

.wrapper h1.title{
  font-size: 80px;
  font-style: italic;
  font-weight: 700;
  padding-bottom: 10px;
  color: rgba(83, 77, 20, 0.8);
  text-shadow: 3px 3px 10px #666;
}

.wrapper form.todo-form{
  display: flex;
  width: 100%;
  border-top: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
  padding: 30px 0px 20px 0px;
  flex-direction: row;
  justify-content: center;
}
.wrapper form.todo-form input{
  height: 40px;
  flex: 1;
  border: none;
  border-bottom: 1px solid #969696;
  background-color: inherit;
  box-sizing: border-box;
  outline: none;
  padding-left: 10px;
  font-size: 18px;
}
.wrapper form.todo-form button{
  height: 40px;
  /* width: 80px; */
  /* line-height: 40px; */
  font-size: 18px;
  border: none;
  /* margin-left: 10px; */
  margin-top: 5px;
  cursor: pointer;
  background-color: green;
  color: white;
  box-shadow: 2px 2px 10px #000;
  border-radius: 10px;
  outline: none;
  letter-spacing: 1.2px;
}
.wrapper form.todo-form button:hover{
  transform: translateY(-3px);
  transition: all 0.4s;
}

.wrapper ul{
  padding: 20px 20px 10px 20px;
  width: 100%;
  border: 1px solid #ccc;
  border-top: none;
  border-bottom: none;
}
.wrapper ul li{
  width: 100%;
  list-style-type: none;
  border: 1px solid green;
  box-sizing: border-box;
  height: 30px;
  padding: 20px;
  border-radius: 10px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  font-size: 18px;
  margin-bottom: 10px;
}
.wrapper ul li label{
  flex: 1;
  border-left: 1px solid #bbb;
  border-right: 1px solid #bbb;
}
.wrapper ul li label.routine{
  text-decoration: line-through;
}
.wrapper ul li input{
  height: 20px;
  width: 20px;
  margin-right: 10px;
}
.wrapper ul li span{
  font-size: 24px;
  cursor: pointer;
  font-weight: 600;
  color: red;
  padding-left: 10px;
}
#footer{
  /*width: 420px;*/
  width: 100%;
  font-size: 16px;
}

#footer button{
  /*width: 125px;*/
  /*height: 35px;*/
}

#footer-left{
  display: flex;
  width: 100%;
  justify-content: space-between;
  margin: 20px 20px;
  /*float:left;*/
  /*margin-top: 10px;*/

}

#footer-left input{
  float: left;
}

#footer-right{
  display: flex;
  width:100%;
  justify-content: space-between;
  align-items: center;
  margin: 20px 20px;
}

#footer-right span{
  display: inline-block;
  /*margin-top: 10px;*/
  /*margin-right: 25px;*/
}

扩展来着

github.com/G-WangZH/Re…

  • useState() 管理状态的函数,返回一个数组。
  • useEffect() 这个函数可以处理副作用。而且可以用这个函数来模拟类组件中的生命周期函数 —— componentDidMountcomponentDidUpdata以及componentWillUnmount
  • useRef() 这个函数可以是我们在函数组件中获取DOM元素。
  • useContext() 这个函数可以更好的在函数组件中使用context

Hooks 的几个 API 介绍

useState

这个方法,可以使你能在函数组件中使用状态。它有一个参数:状态的初始值,并返回一个数组,这个数组有两个元素,分别是这个状态的变量名和操作这个状态的方法。用法如下:

const [counter,setCounter] = useState(0);

注意这里的写法,看着有点怪,其实是用到了 ES6 中的解构赋值。这句话相当于:

const counterAry = useState(0),
        counter = counterAry[0],
        setCounter = counterAry[1];

counter和setCounter变量名是可以随便取的。useState(0) 中的 0 就是将初始化 counter 的值为 0。

setCounter(第二个元素)的用法:

在类组件中,我们不能使用setState直接改变state的值,在useState中也是如此。比如,我想让状态 counter 加 1,你不能这么干:

setState({
    counter: counter ++
});

而应:

setState({
    counter: counter + 1
});

setCounter 是一个方法,里面可以传入一个语句,但不可以直接改变 state。应该这么来写:

setCounter(counter + 1);

以上就是 useState() API 的用法。接下来是 useEffect

useEffect()

该方法,比 useState() 方法能理解一些。可以将副作用编写到该方法当中,比如:异步请求、DOM事件。在React的类组建当中,常把副作用编写到 componentDidMount 和 componentDidUpdata 两个生命周期函数中。componentDidMount 表示组件已经挂载完毕,这个函数只会被调用一次在组件生命周期中,因此成为异步请求不错的发起位置。而 componentDidUpdata 函数在函数每次更新后几乎都会被调用(当然除了使用shouldComponentUpdata 或者 memo函数可能不会更新组件),因此使用该函数可以完成一些频繁的副作用操作,比如:DOM的滚动事件、窗口事件等使状态频繁被触发的情况肯能就会用到。
那么在函数组件的Hook中又如何来实现呢?

useEffect() 函数模拟 componentDidMount 函数

useEffect函数接收两个参数,一个是 callback 另一个是个数组(可选)。这个数组很关键,它关系到前面的提到的生命周期函数的实现。先说第一个callback参数。这个参数就是为了处理副作用所设计的回调函数。在里面可以很好的处理副作用:

useEffect(() => {
    axios.get('www.example.com/aaa?xxx').then((res) => {
        // ....
    });
});

第二个参数是个可选的参数,如果不写,则说明 每次挂载以及更新操作都会被出发回调函数。这也就相当于 componentDidMount + componentDidUpdata 。而如果填入一个空数组 ([]) 则表示你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。(相当于只在 componentDidMount 中执行)。如果传入元素,必须传入的是 state 或 props 变量,意思就是只有当 state 或 props 值改变才会触发回调函数,这麽做可以避免不必要的重复渲染。

useRef

useRef 方法可以很方便的才函数组件中获取DOM。它返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

访问子组件:

function Example(){
    const div = useRef();
    useEffect(() => {
        div.current.innerText = 'Hooks';
    },[]);
    return (
        <div ref={div}></div>
    );
}

在组件中ref值等于什么变量,在外部调用时变量名应一致,不然会报错。

useContext

useContext 方法相当于类组建当中的 static contextType = MyContext 以及 const context = this.context 两个语句。或者早期的 <Context.Consumer>。使用 useContext() 函数使代码更加简洁。语法:

const value = useContext(MyContext);

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。当然,前面的 <Provider> 和创建 context 方式并没有变,只是后面获取 value 值时更见方便了。

useCallback

 import React,{ useCallback,memo} from 'react'
 const [state,setState] = useState("我是谁啊")
 const A = useCallback(() =>{
 },[state])
  const A = memo(() =>{
 },[state])

该方法是为了优化函数的。与 memo 很像,只不过 useCallback 是为了不它的参数(一个函数)做不必要的重复调用,这个函数是个纯函数,可根据传入的props来判断要不要重复执行函数。因为在纯函数中,相同的参数返回的结果是相同的,因此没有必要对相同的参数做重复调用,也就避免的不必要的渲染。它的返回值还是它的回调函数,但是这个返回的函数具有记忆功能,就像React中的memo函数。

现在我们重新梳理一下业务逻辑:

  1. 输入框输入内容,点击添加按钮,添加到列表里
  2. 列表里的数据可以使用多选按扭对事项完成做标注,还可以删除单项数据
  3. 下方按钮可以做全选或者取消全选,点击删除完成按钮可以对选中的列表数据进行在全部删除

第一种 未封装hooks 使用 useContext 做参数传递

模型部分

import React from 'react';
import './App.css';

function TodoList(){
    return (
        <div className="wrapper">
            <h1>todoList</h1>
            <Header />
            {/* Header 用来盛放输入以及提交按钮 */}
            <List />
            {/* List 用来盛放任务列表 */}
        </div>
    );
}

function Header(props){
    return (
        <form className="todo-form">
        <input ref={ipt} type="text" placeholder="请输入代办事务" />
        <button
            onClick={handleAddItem}
            type="submit"
        >submit</button>
    </form>
    );
}

function List(props){
    return (
        <ul>
            {/*
                这里渲染的每一项事务:<Item />
            */}
        </ul>
    );
}

function Item(props){       // 渲染列表
    return (
        <li className="todo-item">
        <input
            onChange={handleChange}
            type="checkbox"
        />
        <label>{info.item}</label>
        <span
            onClick={handleRemove}
            className="remove"
        >&#215;</span>
    </li>
    );
}
const Footer = () =>{
    return(
        <div id="footer">
            <div id="footer-left">
                <input
                    type="checkbox"
                    onChange={checkListtodo}
                />
                <span>全选/取消全选</span>
            </div>
            <div id="footer-right">
                <div>已完成{0}项/总计{0}件</div>
                <div>
                    <Button onClick={learallListchecked}>删除已完成</Button>
                </div>
            </div>
        </div>
    )
}
export default App;

逻辑部分--拆分函数

现在我们发现所有的函数处理都放在了App函数里面那我们就拆App里面的函数这样我们就可以很直观看到如何处理的增删改

 // header 只需要处理一项业务就行,新增数据,输入框的处理在header组件内部处理就行
/**
 * 就是当输入相同的内容时,List 不应该渲染相同的数据项,
 * 因此需要去重,只需在 addItem 重新写一些去重即可:
 * */
 // info 是list 里面的添加每一项做比较是否添加
const addItem = useCallback((info) => {
    // 这里使用扩展运算符,实现浅拷贝
    // 因为 state 不能直接做更改
    // find 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
    if(itemList.find((ele) =>ele.name === info.name)){
        return;
    }
    setitemList([...itemList, info])
}, [itemList]) // 对当前值的更改变化的话会调用

=========================================================================

// List  只需要处理一项业务就行,删除数据removeItem,传递一个数组列表itemList,更改勾选标注的状态toggle
const removeItem = useCallback((id) => {
    setitemList(itemList.filter(value => value.id !== id));
}, [itemList])

=========================================================================

const toggle = useCallback((id) => {
    // 根据 ID 变换事务的完成状态
    // 这里使用 map 在map前,使用concat方法实现数组拷贝
    setitemList([].concat(itemList).map((item) => {
        // 根据id判断
        // 点击 checkbox 复选框时,完成变成未完成
        // 未完成可变成完成状态
        // 改成haveDone 就可以标注了
        if (item.id === id) {
            item.haveDone = !item.haveDone
        }
        // 返回最新item
        return item
    }))
}, [itemList])

==========================================================================
Item

Footer 我们要做全选checkListtodo,还要实现对选中的数据进行删除clearallListchecked
// 全选列表逻辑
const checkListtodo = (id) =>{
    itemList.forEach((todo) => todo.haveDone = !slectedAll);
    setitemList(itemList);
    setSlectedAll(!slectedAll);
    if(itemList.length === 0){
        alert("现在还没有任务了,请先添加任务!")
    }
    const newTodos = itemList.filter((todo) => todo.haveDone === true);
    setFinishCount(newTodos.length);
}

// 清除选择的任务在列表中
const clearallListchecked = () =>{
    const newTodos = itemList.filter((todo) => todo.haveDone === false);
    console.log(newTodos,"+++++++++++++++++====>")
    setitemList(newTodos);

    const newTodosFnished = newTodos.filter((todo) => todo.haveDone === true);
    setFinishCount(newTodosFnished.length);

    const isFinished = itemList.filter((todo) => todo.haveDone === true);
    // 当前删除的值是我勾选后,做的逻辑处理
    console.log(isFinished,"=========>>>>")
    if(isFinished.length === 0){
        alert("现在还没有已经完成的任务哦😯!")
    }
}

Item 页面不做详细讲解: 将info={...item}传递给Item 渲染 06650620-9610-4b25-847D-60C80015579F.png

现在已经把 函数逻辑部分整理完成了...

现在来分析一下几个逻辑处理部分

image.png

这个地方我们是怎么处理呢的,整体的逻辑不好处理不好理解的话,我们拆分一个单元或者模块来理解

// ref 可以获取到输入框value的值,如何获取呢?
const ipt = useRef(null);
// form 表单提交会默认跳转页面
const handleAddItem =() =>{
e.preventDefault();
// 判断 输入框里是否有值
// add 事件
    ...
    if (!ipt.current.value) {
        // 如果提交时发现input 框没值, 则什么都不做
        return
    }
    // 添加完后清空输入框
    ipt.current.value = ""

}

<input ref={ipt} type="text" placeholder="请输入待办事务"/>
<Button onClick={handleAddItem} type="primary">
    submit
</Button>

我们再来看这样一个场景

image.png

可以看到如果要实现一个勾选必须要实现一个列表

image.png

我们看到了一个陌生的字眼:UseContext哪里来的呢? 而又如何让子组件获取到的呢?

 //全局定义:
const TodoContext = createContext(0)
//App组件
const [itemList, setitemList] = useState([])
// 消费者 Provider-0.......................
<TodoContext.Provider value={{
    itemList
    checkListtodo,clearallListchecked
}}>
    {/* 要订阅的组件*/}
    <List/>
    <Footer/>
 </TodoContext.Provider>

//List 组件里面拿到方法和值
 
const Item = (props) => {
    const context = useContext(TodoContext)
    // const {remove, toggle, info} = props
    const info = props.info;

    // 切换事务状态
    const handleChange = () => {
        // 根据 info =>id移除
        context.toggle(info.id)
    }
    // 移除事务
    const handleRemove = () => {
        context.removeItem(info.id)
    }
    return (
        <li className="todo-item">
            {/* 没有进行标注是因为单词写错了haneDone =>haveDone*/}
            <Checkbox
                onChange={() =>handleChange()}
                checked={info.haveDone}
            />
            {/* 根据状态切换*/}
            <label
                className={info.haveDone ? "routine" : ""}
            >{info.name}</label>
            <span onClick={handleRemove} className="remove">
                &#215;
            </span>
        </li>
    )
}

然后我们来看看这个场景是如何实现的

image.png

当前功能涉及到全选和取消全选, 删除已经选中的 首先我们通过中间件去传递方法给Footer组件

image.png 获取方式 image.png

这样hooks 相关的待办事项已经完成了

再补充一下使用封装hooks如何实现待办事项

第二种 使用封装hooks如何实现待办事项

整体代码hooks - useInputState.js

import { useState } from 'react';

export default () => {
  const [value, setValue] = useState('');

  return {
    value,
    onChange: event => {
      setValue(event.target.value);
    },
    reset: () => setValue('')
  };
};

整体代码hooks - usetodoListState.js

import { useState } from 'react';

export default initialValue => {
  const [todos, setTodos] = useState(initialValue);
  const [finishCount,setFinishCount]= useState(0);
  const [slectedAll,setSlectedAll]= useState(false);

  return {
      todos,
      finishCount,

    addTodo: todoText => {
        setTodos([...todos, todoText]);
    },

    deleteTodo: todoIndex => {
        const newTodos = todos.filter((todo, index) => index !== todoIndex);
        setTodos(newTodos);

        const newTodosFnished = newTodos.filter((todo) => todo.flag === true);
        setFinishCount(newTodosFnished.length);

    },

    slectAllOrNot: () => {
        todos.forEach((todo) => todo.flag = !slectedAll);
        setTodos(todos);
        setSlectedAll(!slectedAll);

        if(todos.length === 0){
            alert("现在还没有任务了,请先添加任务!")
        }

        const newTodos = todos.filter((todo) => todo.flag === true);
        setFinishCount(newTodos.length);
    },

    deleteSelectedTodo: () => {
        const newTodos = todos.filter((todo) => todo.flag === false);
        setTodos(newTodos);

        const newTodosFnished = newTodos.filter((todo) => todo.flag === true);
        setFinishCount(newTodosFnished.length);

        const isFinished = todos.filter((todo) => todo.flag === true);
        if(isFinished.length === 0){
          alert("现在还没有已经完成的任务哦😯!")
        }
    },

    changeTodoFinished:todoIndex => {
        todos[todoIndex].flag = !todos[todoIndex].flag;
        setTodos(todos);

        const newTodosFnished = todos.filter((todo) => todo.flag === true);
        setFinishCount(newTodosFnished.length);
    },

  };
};

渲染组件 index.js

import React from 'react';
import ReactDOM from 'react-dom';

import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';
import TodoFooter from "./components/TodoFooter";
import useTodoState from './hooks/useTodoState';

import './index.css';

const App = () => {
  const { todos, addTodo, deleteTodo,slectAllOrNot,deleteSelectedTodo,changeTodoFinished,finishCount} = useTodoState([]);

  return (
    <div className="App">
        <h1>TodoList</h1>

        <TodoForm
            saveTodo={todoText => {
                // “trim() 函数移除字符串两侧的空白字符或其他预定义字符
              const trimmedText = todoText.trim();

              if (trimmedText.length > 0) {
                addTodo({title:trimmedText,flag:false});
              }
            }}
        />

        <TodoList
            todos={todos}
            deleteTodo={deleteTodo}
            changeTodoFinished={changeTodoFinished} />

        <TodoFooter
            todos={todos}
            slectAllOrNot={slectAllOrNot}
            deleteSelectedTodo={deleteSelectedTodo}
            finishCount={finishCount}
        />
    </div>
  );
};

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

子组件 TodoForm

import React from 'react';
import useInputState from '../hooks/useInputState';

const TodoForm = ({ saveTodo }) => {
  const { value, reset, onChange } = useInputState();

  return (
    <form
      onSubmit={event => {
        event.preventDefault();
        saveTodo(value);
        reset();
      }}
    >
        <input type="text"
               placeholder="请输入待办事项,按回车键添加"
               onChange={onChange}
               value={value}
               id="header-input"
        />
    </form>
  );
};

export default TodoForm;

子组件 TodoList

import React from 'react';

const TodoList = ({ todos, deleteTodo,changeTodoFinished }) => {

    if(todos.length === 0){
        return (<h2>暂时没有任务!请添加新任务!</h2>)
    }

    return (
        <ul>
            {todos.map((todo, index) => (
                <li key={index}>
                    <input
                        type="checkbox"
                        checked={todo.flag}
                        onChange={() => {
                            changeTodoFinished(index)
                        }}
                    />
                    <span>{index}</span>
                    {todo.title}
                    <button onClick={() => {
                        deleteTodo(index);
                    }}>删除
                    </button>
                </li>
            ))}
        </ul>
    )
};

export default TodoList;

子组件 TodoFooter

import React from "react";

const TodoFooter = ( { todos,slectAllOrNot,deleteSelectedTodo,finishCount } )=>{

    return (
        <div id="footer">
            <div id="footer-left">
                <input
                    type="checkbox"
                    onChange={slectAllOrNot}
                    checked={finishCount === todos.length && todos.length > 0}
                />
                <span>全选/取消全选</span>
            </div>
            <div id="footer-right">
                <span>已完成{finishCount}项/总计{todos.length}件</span>
                <button onClick={deleteSelectedTodo}>删除已完成</button>
            </div>


        </div>
    );
};

export default TodoFooter;

全局样式 index.css

.App {
  font-family: sans-serif;
  text-align: center;
  margin:0 auto;
  width: 800px;

}

#header-input {
  width: 800px;
  height: 40px;
  font-size: 16px;
  border-radius: 5px;
  border:1px solid lightskyblue;
}

ul {
  list-style: none;
  margin-top: 30px;
  border: 1px solid lightskyblue;
  border-radius: 5px;
  text-align: center;
  vertical-align: center;
}

ul li {
  width: 800px;
  height: 50px;
  border-bottom: 1px solid lightskyblue;
  margin-left: -42px;
  text-align: center;
  line-height: 50px;

}

ul li:hover{
  background-color: lightskyblue;
}

li span{
  float: left;
}

li input{
  float: left;
  margin-top: 19px;
  margin-left: 10px;
  margin-right: 10px;
}

button {
  width: 65px;
  height: 35px;
  border-radius: 5px;
  background-color: #ff3333;
  float: right;
  margin-right: 10px;
  margin-top: 5px;
  color:#ffffff;
  font-size: 16px;
  cursor: pointer;
}

#footer{
  width: 800px;
  font-size: 16px;
}

#footer button{
  width: 125px;
  height: 35px;
}

#footer-left{
  float:left;
  margin-top: 10px;

}

#footer-left input{
  float: left;
  margin-left: 10px;
  margin-right: 10px;
}

#footer-right{
  float: right;
}

#footer-right span{
  display: inline-block;
  margin-top: 10px;
  margin-right: 25px;
}

最终效果

image.png

解析组件- input 输入框

image.png

1.这里我们定义了(初始化)一个value值,用来获取输入框的值,后者的方法用来更新value值 2. 我们抛出的整个函数体,包含 value值

  return {
   value,
   onChange:event =>{
    setValue(event.tartget.value)
   }, // 获取value的值Input 更新
   reset:() => setValue("")// 重置
  }

TodoForm

import React from 'react';
import useInputState from '../hooks/useInputState'; // 导入hooks

const TodoForm = () => {
  const { value, reset, onChange } = useInputState(); // 引入方法,值

  return (
  // 引入reset 重置方法
    <form
      onSubmit={event => {
        event.preventDefault()
        reset();
      }}
    >
    // 引入onChange 更新获取value值
    // value 的值赋值
        <input type="text"
               placeholder="请输入待办事项,按回车键添加"
               onChange={onChange}
               value={value}
               id="header-input"
        />
    </form>
  );
};

// 现在发现我们好像少了添加,然后我们要在哪里处理呢? 对 hooks-todoList封装了添加的方法

import useTodoState from './hooks/useTodoState';
import React from 'react';
import ReactDOM from 'react-dom';
import TodoForm from './components/TodoForm';
import './index.css';
const App = () => {
  const { addTodo} = useTodoState();
      return (
            <div className="App">
                <h1>TodoList</h1>
                 // 如果当前输入的值不是空的情况开始添加
                                         // “trim() 函数移除字符串两侧的空白字符或其他预定义字符
                <TodoForm
                    saveTodo={todoText => {
                      const trimmedText = todoText.trim();
                      if (trimmedText.length > 0) {
                        addTodo({title:trimmedText,flag:false});
                      }
                    }}
                />
             </div>
        )
    }

解析组件-标记选中未选中 删除

TodoList

import React from 'react';

const TodoList = ({ todos, deleteTodo,changeTodoFinished }) => {

    if(todos.length === 0){
        return (<h2>暂时没有任务!请添加新任务!</h2>)
    }

    return (
        <ul>
            {todos.map((todo, index) => (
                <li key={index}>
                    <input
                        type="checkbox"
                        checked={todo.flag}
                        onChange={() => {
                            changeTodoFinished(index)
                        }}
                    />
                    <span>{index}</span>
                    {todo.title}
                    <button onClick={() => {
                        deleteTodo(index);
                    }}>删除
                    </button>
                </li>
            ))}
        </ul>
    )
};

export default TodoList;

====================================================================

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './components/TodoList';
import useTodoState from './hooks/useTodoState'; // 相关方法封装hooks

import './index.css';
const App = () => {
  const { todos,deleteTodo,changeTodoFinished} = useTodoState([]);
  return (
    <div className="App">
        <h1>TodoList</h1>
        <TodoList
            todos={todos}
            deleteTodo={deleteTodo}
            changeTodoFinished={changeTodoFinished} />
    </div>
  );
};

解析组件-删除选中及全选功能

TodoFooter

import React from "react";

const TodoFooter = ( { todos,slectAllOrNot,deleteSelectedTodo,finishCount } )=>{

    return (
        <div id="footer">
            <div id="footer-left">
                <input
                    type="checkbox"
                    onChange={slectAllOrNot}
                    checked={finishCount === todos.length && todos.length > 0}
                />
                <span>全选/取消全选</span>
            </div>
            <div id="footer-right">
                <span>已完成{finishCount}项/总计{todos.length}件</span>
                <button onClick={deleteSelectedTodo}>删除已完成</button>
            </div>


        </div>
    );
};

export default TodoFooter;


============================================================

import React from 'react';
import ReactDOM from 'react-dom';
import TodoFooter from "./components/TodoFooter";
import useTodoState from './hooks/useTodoState';
import './index.css';

const App = () => {
  const { todos,slectAllOrNot,deleteSelectedTodo,finishCount} = useTodoState([]);

  return (
    <div className="App">
        <h1>TodoList</h1>
        <TodoFooter
            todos={todos}
            slectAllOrNot={slectAllOrNot}
            deleteSelectedTodo={deleteSelectedTodo}
            finishCount={finishCount}
        />
    </div>
  );
};

封装hooks待办事项完成,方法部分很简单不多讲解