React笔记(四)
前言
eg:代表代码对照 若文章有误,欢迎读者留言反馈知识点回顾
- 插值 {} 变量,表达式,函数调用
- 动态属性 属性={ 变量 }
- 样式写法
className={ 变量 }
className={'active btn'}
className={ arr.join(' ') }
style={{ fontSize:'12px',color:变量 }}
- 循环
1
2
3
4
5
6
7
8
9
10<ul>
{arr.map((item,index)=>{
return <li key={ item.id }>{ item }</li>})
}
</ul>
<ul>
{Object.keys(obj).map((item,index)=>{
return <li key={ item }>属性:{ item },属性值:{ ob[item] }</li> })
}
</ul> - 组件基本认识
- 函数组件就是函数,函数名称大写,有return值,return的是jsx,函数没有实例,没有生命周期,没有状态,没有this, 函数组件我们也可以叫纯函数,参数不变的情况下,函数组件也不会变化。
- 类组件就继承react中的父类,重写了render函数,函数中有return值,return是jsx。
- 组件中jsx必须有唯一的跟标签,组件名称遵循大驼峰
- props
- 类组件中可以this.props.属性 可以获取父组件的值。
- 函数组件通过props.属性获取父组件的值。
- props只读,单项数据流,父组件更新数据,props中的数据也随之更新。props就是父组件流转给子组件的数据。
- 事件
- 语法:on + 事件类型(首字符大写) = 执行函数
- event 事件对象是混合事件对象
- react的事件对象和原生事件对象区别?
- react为了更好的兼容性和跨平台。为了事件的统一管理,所有的事件绑定在document上,避免频繁解绑,提来性能。
this问题:React事件在定义的时候,默认情况下是没有给事件传递this进去【这个this就是当前组件,如何获取到它?】
产生问题代码:
1 | // jsx模板 |
解决方法:
- 在函数调用的时候传递this进去
- 在类构造器中通过bind更改this指向
- 使用箭头函数【推荐】
- 在函数调用的时候传递this进去
- 回调传递this【传递参数需要回调,不回调默认执行一次】
1
2
3
4
5
6
7
8// jsx模板
{/* this传递进去【传递参数写法】 */}
<button onClick={() => this.handleClickB(this)}>点击B</button>
// 定义事件函数
handleClickB() {
console.log('this', this) // 成功拿到this,当前组件
} 通过bind改变this指向,为什么不使用call和apply改变this指向?call和apply会默认调用一次函数,而bind不会
1
2
3
4
5
6
7
8// jsx模板
{/* 通过bind,为什么不使用call和apply改变this指向?call和apply会默认调用一次函数,而bind不会 */}
<button onClick={this.handleClickC.bind(this)}>点击C</button>
// 定义事件函数
handleClickC() {
console.log('this', this) // 成功拿到this,当前组件
}
在类构造器中通过bind更改this指向
1
2
3
4
5
6
7
8
9
10
11
12
13
14// jsx模板
{/* 在类构造器中通过bind更改this指向 */}
<button onClick={this.handleClickD}>点击D</button>
// 定义事件
// class类构造器
constructor(props) {
super(props) // 调用super(props),用来将父组件传来的props绑定到继承类中。
this.handleClickD = this.handleClickD.bind(this)
}
// 定义事件函数
handleClickD() {
console.log('this', this) // 成功拿到this,当前组件
}使用箭头函数【推荐】
1
2
3
4
5
6
7
8// jsx模板
{/* 事件定义使用箭头函数 */}
<button onClick={this.handleClickE}>点击E</button>
// 定义事件函数
handleClickE = () => {
console.log('this', this) // 成功拿到this,当前组件
}
总结:传递参数使用箭头函数写法,直接写会默认给你执行一次(不传参可直接写不要加小括号,主要是加了小括号会默认执行一次),事件定义时也选择箭头函数写法
eg1:
1 | import React, { Component } from 'react' |
State状态操作
概念性知识:
类组件可以放状态,函数组件不能。
- 构造器定义我们的状态
- state定义属性来定义状态
- state状态要遵循不可变值,state的数据,你不能直接修改改。
- setState方法来修改state的值。react管理的地方是异步方法。
更新state中的数据
更新state状态【遵循不可变值:不能更改state里面的值,再覆盖,主要针对于引用值,可用一个跟它不相关的数据覆盖[深拷贝或一个新数据]】
使用方法this.setState(),更新state的值【实际上是与state合并,覆盖】定义状态变量写法1【推荐】
1 | state = { // 等价于vue中的data,定义我们的状态变量,状态都是响应式 |
定义状态变量写法2【利用构造器函数】
1 | constructor(props) { |
更新state状态【遵循不可变值:不能更改state里面的值】
基本数据类型更新
1 | // 定义事件函数 |
引用数据类型更新
- 数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
addHandleD = () => {
// 深拷贝state中的arr
var myarr = JSON.parse(JSON.stringify(this.state.arr))
myarr.push(6)
this.setState({
// 错误方法:使用能够改变原数组的方法【只要是改变原数组的方法都不能在这里直接使用,需要通过深拷贝,不对state中的数据造成影响】
// arr: this.state.arr.push(6) // 改变了state中的arr ----- 错误
// 方法1:使用不改变原数组的方法如concat,里面参数6或者[6]都可以
// arr: this.state.arr.concat(6) // arr不是state中的arr(合并覆盖)-----正确
// 方法2:使用...
// arr: [...this.state.arr, 6] // arr不是state中的arr(合并覆盖)-----正确
// 方法3:深拷贝一份原数组中的数据[在setState方法外面],这样就可以使用数组的所有方法,不会对state中的数组造成影响
arr: myarr // arr不是state中的arr(合并覆盖)-----正确
})
}
对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14addHandleE = () => {
// 深拷贝state中的obj
var myobj = JSON.parse(JSON.stringify(this.state.obj))
this.setState({
// 同理与数组思路一样
// 方法1:使用...
// obj: {...this.state.obj, name: '小红'} // obj不是state中的obj(合并覆盖)-----正确
// 方法2:使用不改变原对象的方法如Object.assign({},this.state.obj,{name: '小张'})
// assign和数组concat是一样的做对象键名合并,assign是把参数2之后的所有对象合并到参数1对象中,并且返回参数1
// obj: Object.assign(this.state.obj, {name: '小红'}) // 正确
// 方法3:深拷贝一份原对象中的数据
obj: {...myobj, name: '小红'} // 正确
})
}
eg2:
1 | import React, { Component } from 'react' |
面试题:setState是同步还是异步,是合并还是不合并?
答案:
1.react管理的地方是异步方法。在回调,异步是同步的。React@18 自定义事件中setState是异步,React@17.0.2是同步。
2.setState参数是对象的形式下是默认合并的。 参数是函数情况下是不合并的
同步能获取最新值,异步拿不到最新值上一段中更新数据实际上是默认合并了,因为我们往setState里面传递的是对象
React管理的地方是异步方法
1
2
3
4
5
6
7handleClick = () => {
console.log('0', this.state.num)
this.setState({ num: this.state.num + 1 })
this.setState({ num: this.state.num + 1 })
this.setState({ num: this.state.num + 1 })
console.log('3', this.state.num) // 0
}自定义事件
1
2
3
4clickHandle = () => {
this.setState({num:this.state.num+1})
console.log('4',this.state.num); // 0 【React@17.0.2版本:自定义事件中,setState是同步的,获取最新值1。
}setState的回调
1
2
3
4
5
6
7
8
9handleClick = () => {
console.log('0', this.state.num)
this.setState({ num: this.state.num + 1 })
this.setState({ num: this.state.num + 1 })
this.setState({ num: this.state.num + 1 }, () => {
// 当前是setState的回调函数,这里面是同步,能获取最新的值
console.log('2', this.state.num)
})
}异步中获取state值
1
2
3setTimeout(() => { // 异步中获取最新的state的值
console.log('获取最新state值', this.state.num)
}, 0)setState合并操作
1
2
3
4
5
6
7
8
9
10
11// this.setState是异步操作,多次异步操作,修改的属性相同会做合并操作。只有最后一个生效
// Object.assign({ num: this.state.num + 1},{ num: this.state.num + 1},{ num: this.state.num + 1})
this.setState(state => { //每次state都是最新的state所以不会合并
return { num: state.num + 1 }
})
this.setState(state => {
return { num: state.num + 1 }
})
this.setState(state => {
return { num: state.num + 1 }
})
props进阶=>组件通信
this.props.children获取父组件中占位符【这个占位符是指当前子组件在父组件中的占位容器】闭合标签中的内容[获取innerHTML]
this.props.children类似vue里面的匿名插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 父组件jsx模板
state = {
num: 123
}
class Parent extends Component {
render() {
return (
<div>
<h3>props进阶</h3>
<Child>
<i>我是父组件中的文本</i>
<p>{this.state.num}</p>
</Child>
</div>
)
}
}
// 子组件jsx模板
class Child extends Component {
render() {
return (
<div>
<h3>Child</h3>
{/* this.props.children获取父组件中占位符【这个占位符是指当前子组件在父组件中的占位容器】闭合标签中的内容[获取innerHTML] */}
{this.props.children}
</div>
)
}
}
类型限定
prop-types
,props默认值安装插件
npm i -S prop-types
引入
prop-types
包import PropTypes from 'prop-types'
- 方法1:类组件和函数组件都适用【写在export外面】
1 | // 方法1 类组件和函数组件都适用 |
- 方法2:只适用于类组件【写在export里面】
1 | static propTypes = { |