「本文已参与低调务实优秀中国好青年前端社群的写作活动」。
写在前面
在最近看了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.状态在哪里,操作状态的方法就在哪里