写在前面
在最近看了React之后,一直觉得学的懵懵然,虽然很多大佬的手写笔记,写的都很不错,但是我一直没有我想要的那种细无巨细,比如类式组件this指向问题的追根溯源,又比如三大实例属性简写的由来,总之我还是决定做一份事无巨细的笔记。
那就让我们开始吧!
TodoList案例_静态组件
效果展示
拆分组件: 拆分界面,抽取组件
- Header、List、Item、Footer
实现静态组件: 使用组件实现静态页面效果
- 将静态页面全部复制到App组件,进行详细拆分。
对class、fontsize、style等关键字进行替换为className、fontSize、style={{}}。
- 创建App.css文件,将全部css样式引入,在App组件中引入样式文件。实现样式加工。
- 将对应的html拆分到子组件内部,在App组件中引入然后使用。
注意:Item子组件在List子组件中使用时,使用几个Item子组件,就可以产生几个对应的html结构。
引入包的顺序
- 第三方的包排序在前,自己写的包排序在后面。
样式拆分
- 创建App.css文件,将全部css样式引入,在App组件中引入样式文件。实现样式加工。
- 在每一个子组件文件夹下面创建index.css,将对应html的css样式代码拆分进index.css。(不用忘记在组件文件中引入index.css文件)
TodoList案例_动态初始化列表
父组件可以给子组件传递标签属性
兄弟组件不可以互相传递标签属性
解决方式
- 将状态书写的共同的父组件内,在A子组件中展示状态数据,在B组件中通过调用函数更新状态。
在子组件中展示状态数据
- App组件
//初始化状态 state = {todos:[ {id:'001',name:'吃饭',done:true}, {id:'002',name:'睡觉',done:true}, {id:'003',name:'打代码',done:false}, {id:'004',name:'逛街',done:false} ]} 复制代码
- List组件
使用展开运算符展开todo属性
render() { const {todos,updateTodo,deleteTodo} = this.props return ( <ul className="todo-main"> { todos.map( todo =>{ return <Item key={todo.id} {...todo}/> }) } </ul> ) } 复制代码
- Item组件
render() { const {id,name,done} = this.props return ( <span>{name}</span> ) } 复制代码
做的事件勾选
- 使用checked
直接写死,不可以切换。
- 使用defaultchecked
可以切换打钩状态。
TodoList案例_添加todo
获取用户的输入
- Header组件
1.为input标签绑定onKeyUp={this.handleKeyUp}
2.由于事件对象和操作对象是一样的,直接使用even.target
3.根据回车判断是否输出输入的值
export default class Header extends Component { //对接收的props进行:类型、必要性的限制 static propTypes = { addTodo:PropTypes.func.isRequired } //键盘事件的回调 handleKeyUp = (event)=>{ //解构赋值获取keyCode,target const {keyCode,target} = event //判断是否是回车按键 if(keyCode !== 13) return console.log(target.value); } render() { return ( <div className="todo-header"> <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/> </div> ) } } 复制代码
更新App组件的状态
实现的基本原理
- 子组件调用函数(父组件通过props传递给子组件的函数)改变父组件的状态
1.在父组件中书写改变状态的函数,并且传递给子组件。
//addTodo用于添加一个todo,接收的参数是todo对象 addTodo = (todoObj)=>{ //获取原todos const {todos} = this.state //追加一个todo const newTodos = [todoObj,...todos] //更新状态 this.setState({todos:newTodos}) } 复制代码
2.子组件调用函数,并且传递数值添加到父组件的状态中(注意传递的产生的一致)
这里传递给addTodo函数的参数是一个对象。
id值生成
1.使用时间戳、使用随机数
- 使用uuid库。生成唯一的标识符。(nanoid是一个比较迷你的库)
终端命令:
// 2选1 npm i nanoid yarn add nanoid 复制代码
- 引入nanoid
import {nanoid} from 'nanoid' // 这里的nanoid是一个函数 复制代码
- 添加一个括号进行调用
id:nanoid() 复制代码
- Header组件部分代码
//键盘事件的回调 handleKeyUp = (event)=>{ //解构赋值获取keyCode,target const {keyCode,target} = event //判断是否是回车按键 if(keyCode !== 13) return //准备好一个todo对象 const todoObj = {id:nanoid(),name:target.value,done:false} //将todoObj传递给App this.props.addTodo(todoObj) } 复制代码
- 实现效果
修复bug
- 添加的todo名字不能为空
//添加的todo名字不能为空 if(target.value.trim() === ''){ alert('输入不能为空') return } 复制代码
- 清空输入框中的输入
//清空输入 target.value = '' 复制代码
TodoList案例_鼠标移入效果
给鼠标绑定事件
- 给Item组件绑定onMouseEnter、onMouseLeave
//鼠标移入、移出的回调 handleMouse = (flag)=>{ return ()=>{ // 修改状态 this.setState({mouse:flag}) } } onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false) 复制代码
- 注意这里的handleMouse使用高阶函数。
- 根据状态的改变进行样式的转换
// item组件 style={{backgroundColor:mouse ? '#ddd' : 'white'}} // 删除按钮的展示 style={{display:mouse?'block':'none'}} 复制代码
- 效果展示
TodoList案例_添加—个todo
改变事件是否勾选的状态
- 为Item组件绑定onCHange事件
<input type="checkbox" checked={done} onChange={this.handleCheck(id)}/> 复制代码
因为绑定事件和要操作的事件是同一个,直接使用event.target而不是使用ref属性。
type="checkbox"这个类型的input标签有一个属性event.target.checked可以获得是否被勾选。
//勾选、取消勾选某一个todo的回调 handleCheck = (id)=>{ return (event)=>{ this.props.updateTodo(id,event.target.checked) } } 复制代码
- update函数(App组件内部)
通过参数id找到对应的对象,并且修改该对象的其他属性(这里是done)。进而达到修改状态的目的。
//updateTodo用于更新一个todo对象 updateTodo = (id,done)=>{ //获取状态中的todos const {todos} = this.state //匹配处理数据 const newTodos = todos.map((todoObj)=>{ if(todoObj.id === id) return {...todoObj,done} else return todoObj }) this.setState({todos:newTodos}) } 复制代码
- 传递函数给子组件List组件、List组件传递给子组件Item组件
<List todos={todos} updateTodo={this.updateTodo} /> <Item key={todo.id} {...todo} updateTodo={updateTodo} /> 复制代码
在Item组件中调用该函数
//勾选、取消勾选某一个todo的回调 handleCheck = (id)=>{ return (event)=>{ this.props.updateTodo(id,event.target.checked) } } 复制代码
- 总结
状态在哪里,操作状态的方法就在哪里(父组件)
调用函数(子组件)
TodoList案例_对props进行限制
引入Props-Type库
- 下载库命名
yarn add prop-types 复制代码
- 引入库到Header组件
import PropTypes from 'prop-types' 复制代码
- 对传入子组件的props进行限制
- Header组件
//对接收的props进行:类型、必要性的限制 static propTypes = { addTodo:PropTypes.func.isRequired } 复制代码
- List组件
//对接收的props进行:类型、必要性的限制 static propTypes = { todos:PropTypes.array.isRequired, updateTodo:PropTypes.func.isRequired, deleteTodo:PropTypes.func.isRequired, } 复制代码
- Item组件
//对接收的props进行:类型、必要性的限制 static propTypes = { todos:PropTypes.array.isRequired, updateTodo:PropTypes.func.isRequired, deleteTodo:PropTypes.func.isRequired, } 复制代码
TodoList案例_删除一个todo
父组件
- 父组件书写一个deleteTodo函数,将状态修改
//deleteTodo用于删除一个todo对象 deleteTodo = (id)=>{ //获取原来的todos const {todos} = this.state //删除指定id的todo对象 const newTodos = todos.filter((todoObj)=>{ return todoObj.id !== id }) //更新状态 this.setState({todos:newTodos}) } 复制代码
子组件
- 子组件绑定点击事件调用deleteTodo
//删除一个todo的回调 handleDelete = (id)=>{ // 一个删除提示框 if(window.confirm('确定删除吗?')){ this.props.deleteTodo(id) } } 复制代码
TodoList案例_实现底部功能
数组API reduce
- 传送门
developer.mozilla.org/zh-CN/docs/…
- 2个参数
第一个参数是回调函数,第二个是初始值。
- 使用reduce遍历数组统计完成个数
const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0) 复制代码
- 所有事件总数
//总数 const total = todos.length 复制代码
完成个数等于总数
- 对Footer组件进行打钩。
1.不能使用defaultchecked (一次性)
页面渲染的第一次,defaultchecked就会被赋值。在之后的状态改变导致完成个数等于总数的时候也不会改变defaultchecked的值。
- 使用checked会报错。(写死)
- 添加onchange事件调用checkAllTodo函数修改状态
- App组件
//checkAllTodo用于全选 checkAllTodo = (done)=>{ //获取原来的todos const {todos} = this.state //加工数据 const newTodos = todos.map((todoObj)=>{ return {...todoObj,done} }) //更新状态 this.setState({todos:newTodos}) } 复制代码
2.Footer组件
//全选checkbox的回调 handleCheckAll = (event)=>{ this.props.checkAllTodo(event.target.checked) } 复制代码
- 要将Item组件中使用的defaultchecked修改为checked。
<input type="checkbox" checked={done} onChange={this.handleCheck(id)}/> 复制代码
修补bug
- 删除全部Item组件之后,未消除打钩
- 解决手段
在给打钩添加一个条件(事件总数不为0)
<input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total !== 0 ? true : false}/> 复制代码
清除全部完成事件
为删除完成事件按钮添加点击事件
- 在父组件中书写删除全部完成事件的函数
//clearAllDone用于清除所有已完成的 clearAllDone = ()=>{ //获取原来的todos const {todos} = this.state //过滤数据 const newTodos = todos.filter((todoObj)=>{ return !todoObj.done }) //更新状态 this.setState({todos:newTodos}) } 复制代码
注意: 数组API filter
- 传送门
developer.mozilla.org/zh-CN/docs/…
- 在Footer组件中使用点击事件回调clearAllDone函数
//清除已完成任务的回调 handleClearAllDone = ()=>{ this.props.clearAllDone() } 复制代码
TodoList案例_总结
1.拆分组件、实现静态组件,注意:className、style的写法
2.动态初始化列表,如何确定将数据放在哪个组件的state中?
——某个组件使用:放在其自身的state中 ——某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升) 复制代码
3.关于父子之间通信:
1.【父组件】给【子组件】传递数据:通过props传递 2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数 复制代码
4.注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
5.状态在哪里,操作状态的方法就在哪里







