说实话,我是抱着学会React可以实践项目的想法,出发的,现在想想有些后悔了,太难了,太耗时了,真的不想坚持,学习完Class组件,还要高阶,终于摆脱烦人的this指向问题了,又来个复杂的,啰里啰嗦的Redux, 问过很多人, 不就是 state 不能直接改变, 需要通过reducer, 但是只给reducer 一个state 他不知道什么时候,执行什么,改变什么,就用到了action, action 只是知道类型了, 在reducer里操作了,发现没有效果呀,我更改的值呢,奥,需要dispatch 方法改变视图更新了,说到这里头都大了都是什么玩意呀,来现在我们一层层的捋,让你不再放弃他,放弃他你会放弃很多money,也许我说的对吧,我们现在通过几个案例来实现一级级增加难度,你会发现,我这么笨的人都会了,你能不会吗?? 来开始了
第一个案例
我们通过Class组件实现一个todolist+antd,我们的目的是巩固一下类组件的使用和使用中会遇到哪些问题??
Class 编写 todoList开始
1.按照页面结构拆分组件
渲染组件 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/>
)
}
- 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">
×
</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;*/
}
扩展来着
useState()管理状态的函数,返回一个数组。useEffect()这个函数可以处理副作用。而且可以用这个函数来模拟类组件中的生命周期函数 ——componentDidMount、componentDidUpdata以及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函数。
现在我们重新梳理一下业务逻辑:
- 输入框输入内容,点击添加按钮,添加到列表里
- 列表里的数据可以使用多选按扭对事项完成做标注,还可以删除单项数据
- 下方按钮可以做全选或者取消全选,点击删除完成按钮可以对选中的列表数据进行在全部删除
第一种 未封装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"
>×</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 渲染
现在已经把 函数逻辑部分整理完成了...
现在来分析一下几个逻辑处理部分
这个地方我们是怎么处理呢的,整体的逻辑不好处理不好理解的话,我们拆分一个单元或者模块来理解
// 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>
我们再来看这样一个场景
可以看到如果要实现一个勾选必须要实现一个列表
我们看到了一个陌生的字眼: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">
×
</span>
</li>
)
}
然后我们来看看这个场景是如何实现的
当前功能涉及到全选和取消全选, 删除已经选中的 首先我们通过中间件去传递方法给Footer组件
获取方式
这样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;
}
最终效果
解析组件- input 输入框
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>
);
};