前言

eg:代表代码对照 若文章有误,欢迎读者留言反馈

知识点回顾

  1. 插值 {} 变量,表达式,函数调用
  2. 动态属性 属性={ 变量 }
  3. 样式写法 className={ 变量 } className={'active btn'} className={ arr.join(' ') }
    style={{ fontSize:'12px',color:变量 }}
  4. 循环
    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>
  5. 组件基本认识
    • 函数组件就是函数,函数名称大写,有return值,return的是jsx,函数没有实例,没有生命周期,没有状态,没有this, 函数组件我们也可以叫纯函数,参数不变的情况下,函数组件也不会变化。
    • 类组件就继承react中的父类,重写了render函数,函数中有return值,return是jsx。
    • 组件中jsx必须有唯一的跟标签,组件名称遵循大驼峰
  6. props
    • 类组件中可以this.props.属性 可以获取父组件的值。
    • 函数组件通过props.属性获取父组件的值。
    • props只读,单项数据流,父组件更新数据,props中的数据也随之更新。props就是父组件流转给子组件的数据。
  7. 事件
    • 语法:on + 事件类型(首字符大写) = 执行函数
    • event 事件对象是混合事件对象
    • react的事件对象和原生事件对象区别?
      • react为了更好的兼容性和跨平台。为了事件的统一管理,所有的事件绑定在document上,避免频繁解绑,提来性能。

this问题:React事件在定义的时候,默认情况下是没有给事件传递this进去【这个this就是当前组件,如何获取到它?】

产生问题代码:

1
2
3
4
5
6
7
// jsx模板
<h3>this问题:React事件定义里拿不到this</h3>
<button onClick={this.handleClickA}>点击A</button>
// 定义事件函数
handleClickA() {
console.log('this', this) // undefined
}

解决方法:

  1. 在函数调用的时候传递this进去
  2. 在类构造器中通过bind更改this指向
  3. 使用箭头函数【推荐】
  1. 在函数调用的时候传递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,当前组件
    }
  1. 在类构造器中通过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,当前组件
    }
  2. 使用箭头函数【推荐】

    1
    2
    3
    4
    5
    6
    7
    8
    // jsx模板
    {/* 事件定义使用箭头函数 */}
    <button onClick={this.handleClickE}>点击E</button>

    // 定义事件函数
    handleClickE = () => {
    console.log('this', this) // 成功拿到this,当前组件
    }

总结:传递参数使用箭头函数写法,直接写会默认给你执行一次(不传参可直接写不要加小括号,主要是加了小括号会默认执行一次),事件定义时也选择箭头函数写法

eg1:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import React, { Component } from 'react'

class ThisDemo extends Component {
// 问题:React事件在定义的时候,默认情况下是没有给事件传递this进去【这个this就是当前组件,如何获取到它?】
/*
解决方法:
1.在函数调用的时候传递this进去
2.在类构造器中通过bind更改this指向
3.使用箭头函数【推荐】
*/
handleClickA() {
console.log('this', this) // undefined
}
handleClickB() {
console.log('this', this) // 成功拿到this,当前组件
}
handleClickC() {
console.log('this', this) // 成功拿到this,当前组件
}

// class类构造器
constructor(props) {
super(props) // 调用super(props),用来将父组件传来的props绑定到继承类中。
this.handleClickD = this.handleClickD.bind(this)
}
handleClickD() {
console.log('this', this) // 成功拿到this,当前组件
}

handleClickE = () => {
console.log('this', this) // 成功拿到this,当前组件
}

render() {
return (
<div>
<h3>this问题:React事件定义里拿不到this</h3>
<button onClick={this.handleClickA}>点击A</button>
{/* this传递进去【传递参数写法】 */}
<button onClick={() => this.handleClickB(this)}>点击B</button>
{/* 通过bind,为什么不使用call和apply改变this指向?call和apply会默认调用一次函数,而bind不会 */}
<button onClick={this.handleClickC.bind(this)}>点击C</button>
{/* 在类构造器中通过bind更改this指向 */}
<button onClick={this.handleClickD}>点击D</button>
{/* 事件定义时使用箭头函数 */}
<button onClick={this.handleClickE}>点击E</button>
</div>
)
}
}

export default ThisDemo

State状态操作

概念性知识:
类组件可以放状态,函数组件不能。

  • 构造器定义我们的状态
  • state定义属性来定义状态
  • state状态要遵循不可变值,state的数据,你不能直接修改改。
  • setState方法来修改state的值。react管理的地方是异步方法。

更新state中的数据

更新state状态【遵循不可变值:不能更改state里面的值,再覆盖,主要针对于引用值,可用一个跟它不相关的数据覆盖[深拷贝或一个新数据]】
使用方法this.setState(),更新state的值【实际上是与state合并,覆盖】

定义状态变量写法1【推荐】

1
2
3
4
5
6
7
8
9
state = { // 等价于vue中的data,定义我们的状态变量,状态都是响应式
num: 0,
str: 'hello',
bool: false,
arr: [1, 2, 3, 4, 5],
obj: {
name: '小明'
}
}

定义状态变量写法2【利用构造器函数】

1
2
3
4
5
6
7
8
9
10
11
12
constructor(props) {
super(props) // 调用super(props),用来将父组件传来的props绑定到继承类中。
this.state = { // 等价于vue中的data,定义我们的状态变量,状态都是响应式
num: 0,
str: 'hello',
bool: false,
arr: [1, 2, 3, 4, 5],
obj: {
name: '小明'
}
}
}

更新state状态【遵循不可变值:不能更改state里面的值】

基本数据类型更新

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
31
32
33
34
35
36
37
38
39
// 定义事件函数
addHandleA = () => {
this.setState({ // 更新state的值【实际上是与state合并,覆盖】
// 不要使用自增自减这种赋值操作,它会改变this.state.num中的值
num: this.state.num + 1 // num不是state中的num,【遵循不可变值】--方式正确
})
}
addHandleB = () => {
this.setState({
str: this.state.str + ' world' // str不是state中的str,【遵循不可变值】--方式正确
})
}
addHandleC = () => {
this.setState({
bool: !this.state.bool // bool不是state中的bool,【遵循不可变值】--方式正确
})
}

// jsx模板
render() {
return (
<div>
<h3>状态操作</h3>
{/* 基本数据类型 */}
<p>数字:{this.state.num}</p>
<button onClick={this.addHandleA}>点击A</button>
<p>字符串:{this.state.str}</p>
<button onClick={this.addHandleB}>点击B</button>
{/* 0默认不显示,转话一下 */}
<p>布尔值:{JSON.stringify(this.state.bool)}</p>
<button onClick={this.addHandleC}>点击C</button>
{/* 引用数据类型 */}
<p>数组:{JSON.stringify(this.state.arr)}</p>
<button onClick={this.addHandleD}>点击D</button>
<p>对象:{JSON.stringify(this.state.obj)}</p>
<button onClick={this.addHandleE}>点击E</button>
</div>
)
}

引用数据类型更新

  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. 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    addHandleE = () => {
    // 深拷贝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
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import React, { Component } from 'react'

class StateDemo extends Component {
// 定义状态变量写法1【推荐】
state = { // 等价于vue中的data,定义我们的状态变量,状态都是响应式
num: 0,
str: 'hello',
bool: false,
arr: [1, 2, 3, 4, 5],
obj: {
name: '小明'
}
}

// 定义状态变量写法2【利用构造器函数】
// constructor(props) {
// super(props) // 调用super(props),用来将父组件传来的props绑定到继承类中。
// this.state = { // 等价于vue中的data,定义我们的状态变量,状态都是响应式
// num: 0,
// str: 'hello',
// bool: false,
// arr: [1, 2, 3, 4, 5],
// obj: {
// name: '小明'
// }
// }
// }


// 更新state状态【遵循不可变值:不能更改state里面的值】
// 基本数据类型更新
addHandleA = () => {
this.setState({ // 更新state的值【实际上是与state合并,覆盖】
num: this.state.num + 1 // num不是state中的num,【遵循不可变值】--方式正确
})
}
addHandleB = () => {
this.setState({
str: this.state.str + ' world' // str不是state中的str,【遵循不可变值】--方式正确
})
}
addHandleC = () => {
this.setState({
bool: !this.state.bool // bool不是state中的bool,【遵循不可变值】--方式正确
})
}

// 引用数据类型更新
// 数组
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(合并覆盖)-----正确
})
}

// 对象
addHandleE = () => {
// 深拷贝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: '小红'} // 正确
})
}
render() {
return (
<div>
<h3>状态操作</h3>
{/* 基本数据类型 */}
<p>数字:{this.state.num}</p>
<button onClick={this.addHandleA}>点击A</button>
<p>字符串:{this.state.str}</p>
<button onClick={this.addHandleB}>点击B</button>
{/* 0默认不显示,转话一下 */}
<p>布尔值:{JSON.stringify(this.state.bool)}</p>
<button onClick={this.addHandleC}>点击C</button>
{/* 引用数据类型 */}
<p>数组:{JSON.stringify(this.state.arr)}</p>
<button onClick={this.addHandleD}>点击D</button>
<p>对象:{JSON.stringify(this.state.obj)}</p>
<button onClick={this.addHandleE}>点击E</button>
</div>
)
}
}

export default StateDemo

面试题:setState是同步还是异步,是合并还是不合并?

答案:
1.react管理的地方是异步方法。在回调,异步是同步的。React@18 自定义事件中setState是异步,React@17.0.2是同步。
2.setState参数是对象的形式下是默认合并的。 参数是函数情况下是不合并的
同步能获取最新值,异步拿不到最新值

上一段中更新数据实际上是默认合并了,因为我们往setState里面传递的是对象

  1. React管理的地方是异步方法

    1
    2
    3
    4
    5
    6
    7
    handleClick = () => {
    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
    }
  2. 自定义事件

    1
    2
    3
    4
    clickHandle = () => {
    this.setState({num:this.state.num+1})
    console.log('4',this.state.num); // 0 【React@17.0.2版本:自定义事件中,setState是同步的,获取最新值1。
    }
  3. setState的回调

    1
    2
    3
    4
    5
    6
    7
    8
    9
    handleClick = () => {
    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)
    })
    }
  4. 异步中获取state值

    1
    2
    3
    setTimeout(() => { // 异步中获取最新的state的值
    console.log('获取最新state值', this.state.num)
    }, 0)
  5. 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. 方法1:类组件和函数组件都适用【写在export外面】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 方法1  类组件和函数组件都适用
// Child是组件名称 PropTypes.number是类型的限定
Child.propTypes = {
num:PropTypes.number, //定义num是number类型
obj:PropTypes.shape({ //自定义对象属性 来进行验证
color: PropTypes.string,
fontSize: PropTypes.number
}),
arr:PropTypes.arrayOf(PropTypes.number).isRequired
}

// 设置默认值【父组件传递的有数据,就使用父组件的数据,如果没有就使用默认值,类似vue】
Child.defaultProps = {
arr:['a','b']
}
  1. 方法2:只适用于类组件【写在export里面】
1
2
3
4
5
6
7
8
9
10
11
12
13
static propTypes = {
num:PropTypes.number, //定义num是number类型
obj:PropTypes.shape({ //自定义对象属性 来进行验证
color: PropTypes.string, // color必须是字符串类型
fontSize: PropTypes.number // fontSize必须为数字类型
}),
arr:PropTypes.arrayOf(PropTypes.number).isRequired // 数组每个元素都是数字类型,必填
}

// 设置默认值【父组件传递的有数据,就使用父组件的数据,如果没有就使用默认值,类似vue】
static defaultProps = {
num: 0
}