前言

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

知识点回顾

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

    • props.children 类似vue中的匿名插槽,获取父组件中子组件占位符闭合标签中的内容。
    • props校验

      • npm i -S prop-types
      • 语法:

        1
        2
        3
        组件名.propTypes = { // 适用于类组件和函数组件
        arr: PropTypes.array // 类型校验 number string bool fun object
        }
      • 类组件专属写法 static propTypes = { num:PropTypes.number }

    • props默认值
      • 语法1: 组件名.defaultProps = { num:0 } // 定义默认值
      • 语法2: static defaultProps = { num:0} // 只适用于类组件
  5. 受控组件和ref使用(非受控组件)

    1
    2
    3
    4
    5
    state = { inputVal:'' }
    <input value={ this.state.inputVal } onInput={ this.handleClick }/>
    handleClick = (e) => {
    this.setState({ inputVal:e.target.value })
    }

    ref使用 简写

    1
    2
    <input value={ this.state.inputVal } ref={ (inputVal)=>{ this.inputVal = inputVal } }/>
    this.inputVal.value // 就可以获取当前input的元素值
  6. 生命周期

    • 挂载阶段:constructor getDerivedStateFromProps render componentDidMount(挂载后常用)
    • 更新阶段:getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate更新后
    • 销毁阶段:componentWillUnmount销毁前
    • 废弃生命周期:componentWillMount 挂载前 componentWillReceiveProps props改变就执行 componentWillUpdate更新前
  7. 组件通信
    • 父子通信
      • 父组件传递属性变量或者函数给子组件,子组件使用或者调用函数来实现父子通信。
    • 跨层级通信
    • eventBus通信,事件总线通信
    • redux

React的受控组件

受控组件[类似vue中的双向绑定v-model],input这个元素受state中某个变量的控制【但是需要自己实现】

使用方法:

  1. 定义一个state中变量,元素上使用value属性,把这个值绑定上去
  2. 元素上添加onChange事件,当前元素进行修改的使用,state变量值随之改变。

个人理解:先在state中定义变再量把值绑定到输入框上,再通过事件、事件对象把值取回来存放到state中【双向绑定实现原理】

普通输入框

1
2
3
4
5
6
7
8
9
10
11
12
13
state = {
username:'', // input框用户名
}
// 普通输入框
handleA = (e) => {
this.setState({
username: e.target.value // react没有封装双向绑定,需要我们自己实现
})
}

// jsx模板
<p>{ this.state.username }</p>
<input type="text" value={ this.state.username } onInput={ this.handleA } placeholder='请输入用户名'/>

文本域textarea

1
2
3
4
5
6
7
8
9
10
11
12
13
14
state = {
textField: '', // 文本域
}
// 文本域
handleB = (e) => {
this.setState({
textField: e.target.value
})
}

// jsx模板
{/* 文本域 */}
<p>{this.state.textField}</p>
<textarea value={this.state.textField} onInput={this.handleB}></textarea>

下拉框【它的值在select身上,同时一般结合映射对象,value值会使用1,2,3,通过对象中括号语法映射成需要显示的 字符串】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
state = {
selectVal: '', // 下拉框
}
// 下拉框【它的值在select身上】
handleC = (e) => {
this.setState({
selectVal: e.target.value
})
}

// jsx模板
{/* 下拉框【它的值在select身上】 ,同时一般结合映射对象,value值会使用1,2,3,通过对象中括号语法映射成需要显示的字符串 */}
<select value={this.state.selectVal} onChange={this.handleC}>
<option value="">全部</option>
<option value="上海">上海</option>
<option value="北京">北京</option>
<option value="深圳">深圳</option>
</select>

单选按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
state = {
radioVal: 'male', // 单选按钮
}
// 单选按钮
handleD = (e) => {
this.setState({
radioVal: e.target.value
})
}

// jsx模板
{/* 单选按钮 */}
<p>{this.state.radioVal}</p>
<input type="radio" value="male" onChange={this.handleD} id="male" checked={this.state.radioVal === 'male'}/>
{/* react中用的htmlFor来实现label的绑定,vue中使用的是for这个属性 */}
<label htmlFor="male"></label>
<input type="radio" value="female" onChange={this.handleD} id="female" checked={this.state.radioVal === 'female'}/>
<label htmlFor="female"></label>

复选框【单个使用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
state = {
checkVal: false, // checkbox绑定的值
}
// 复选框单个使用[不绑定value,通过布尔值取反]
handleE = () => {
this.setState({
checkVal: !this.state.checkVal
})
}

// jsx模板
{/* 复选框checkbox */}
{/* 单个使用,不绑定value,通过布尔值取反决定选中状态 */}
<p>{JSON.stringify(this.state.checkVal)}</p>
<input type="checkbox" onChange={this.handleE} checked={this.state.checkVal} />大武汉

复选框【多个使用】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
state = {
checkArr: [], // 选中的checkbox的value的值
}
// 复选框多个使用
handleF = (e) => {
var arr = JSON.parse(JSON.stringify(this.state.checkArr))
var val = e.target.value
var index = arr.findIndex(item => item===val)
if (index === -1) {
arr.push(val)
} else {
arr.splice(index, 1)
}
this.setState({
checkArr: arr
})
}

// jsx模板
{/* 多个使用,绑定value值,通过判断数组是否包含决定选中状态 */}
<p>{JSON.stringify(this.state.checkArr)}</p>
<input type="checkbox" value="Wuhan" onChange={this.handleF} checked={this.state.checkArr.includes('Wuhan')} />大武汉
<input type="checkbox" value="Beijing" onChange={this.handleF} checked={this.state.checkArr.includes('Beijing')} />老北京
<input type="checkbox" value="Shanghai" onChange={this.handleF} checked={this.state.checkArr.includes('Shanghai')} />大上海

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
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 ModelDemo extends Component {
state = {
username: '',//input框用户名
textField: '', //文本域
selectVal: '',//下拉框
radioVal: 'male',//单选按钮
checkVal: false,//checkbox绑定的值
checkArr: [],//选中的checkbox的value的值
}
// 普通输入框
handleA = (e) => {
this.setState({
username: e.target.value // react没有封装双向绑定,需要我们自己实现
})
}
// 文本域
handleB = (e) => {
var arr = JSON.parse(JSON.stringify(this.state.checkArr))
var val = e.target.value
var index = arr.findIndex(item => item === val)
if (index === -1) {
arr.push(val)
} else {
arr.splice(index, 1)
}
this.setState({
checkArr: arr
})
}
// 下拉框【它的值在select身上】
handleC = (e) => {
this.setState({
selectVal: e.target.value
})
}
// 单选按钮
handleD = (e) => {
this.setState({
radioVal: e.target.value
})
}
// 复选框单个使用[不绑定value,通过布尔值取反]
handleE = () => {
this.setState({
checkVal: !this.state.checkVal
})
}
// 复选框多个使用
handleF = (e) => {
var arr = JSON.parse(JSON.stringify(this.state.checkArr))
var val = e.target.value
var index = arr.findIndex(item => item===val)
if (index === -1) {
arr.push(val)
} else {
arr.splice(index, 1)
}
this.setState({
checkArr: arr
})
}
render() {
return (
<div>
<h3>受控组件</h3>
{/* 普通输入框 */}
<p>{this.state.username}</p>
<input value={this.state.username} onInput={this.handleA} type="text" placeholder='请输入用户名' />
{/* 文本域 */}
<p>{this.state.textField}</p>
<textarea value={this.state.textField} onInput={this.handleB}></textarea>
{/* 下拉框【它的值在select身上,同时一般会结合映射对象(自行创建好这个映射对象),value值会使用1,2,3这种,而表单中的各元素取值都是字符串类型,再通过对象中括号语法映射成需要显示的字符串】 */}
<p>{this.state.selectVal}</p>
<select value={this.state.selectVal} onChange={this.handleC}>
<option value="">全部</option>
<option value="上海">上海</option>
<option value="北京">北京</option>
<option value="深圳">深圳</option>
</select>
{/* 单选按钮 */}
<p>{this.state.radioVal}</p>
<input type="radio" value="male" onChange={this.handleD} id="male" checked={this.state.radioVal === 'male'} />
{/* react中用的htmlFor来实现label的绑定,vue中使用的是for这个属性 */}
<label htmlFor="male"></label>
<input type="radio" value="female" onChange={this.handleD} id="female" checked={this.state.radioVal === 'female'} />
<label htmlFor="female"></label>
{/* 复选框checkbox */}
{/* 单个使用,不绑定value,通过布尔值取反决定选中状态 */}
<p>{JSON.stringify(this.state.checkVal)}</p>
<input type="checkbox" onChange={this.handleE} checked={this.state.checkVal} />大武汉
{/* 多个使用,绑定value值,通过判断数组是否包含决定选中状态 */}
<p>{JSON.stringify(this.state.checkArr)}</p>
<input type="checkbox" value="Wuhan" onChange={this.handleF} checked={this.state.checkArr.includes('Wuhan')} />大武汉
<input type="checkbox" value="Beijing" onChange={this.handleF} checked={this.state.checkArr.includes('Beijing')} />老北京
<input type="checkbox" value="Shanghai" onChange={this.handleF} checked={this.state.checkArr.includes('Shanghai')} />大上海
</div>
)
}
}

export default ModelDemo

非受控组件,ref的使用

react中ref 不止获取dom元素,也可以获取子组件的实例

  1. 获取dom元素进而获取元素的值,通过ref内联方式快速获取

ref内联方式,ref传入的实参username,是对该元素的标记===能获取该元素,挂到组件的username属性上,这样就能直接通过this.username获取到该元素

1
2
3
4
5
6
7
handleA = () => {
console.log('获取用户名', this.username.value)
}

// jsx模板
<input ref={username => this.username = username} type="text" placeholder='请输入用户名' />
<button onClick={this.handleA}>登录A</button>
  1. 获取dom元素进而获取元素的值,引入createRef,通过构造器定义一个ref,再去模板上给对应元素绑定ref属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 引入createRef
import React, { Component, createRef } from 'react'

// 2. 定义一个ref出来
constructor(props) {
super(props)
this.pwd = createRef()
}

handleB = () => {
console.log('获取密码', this.pwd.current.value)
}

// jsx模板
{/* 3. 绑定ref属性 */}
<input ref={this.pwd} type="pwd" placeholder='请输入密码'/>
<button onClick={this.handleB}>登录B</button>
  1. 获取子组件里面的数据即state中的值,引入createRef,通过构造器定义一个ref,再去模板上给子组件占位标签绑定ref属性
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
// 父组件
// 1. 引入createRef
import React, { Component, createRef } from 'react'

// 2. 定义一个ref出来
constructor(props) {
super(props)
this.child = createRef()
}

handleC = () => {
console.log('获取子组件中的state', this.child.current.state) // 获取到子组件中state对象
console.log('获取子组件中的state里面的值', this.child.current.state.num) // 100
}

// jsx模板
{/* 获取子组件里面的数据即state中的值 */}
<Child ref={this.child} />
<button onClick={this.handleC}>登录C</button>

// 子组件
class Child extends Component {
state = {
num: 100
}
render() {
return (
<div>
Child
</div>
)
}
}

export default Child

React的生命周期

生命周期:

  • 挂载阶段:constructor getDerivedStateFromProps render componentDidMount(常用)
  • 更新阶段:getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate
             componentDidUpdate更新后
    
  • 销毁阶段: componentWillUnmount销毁前
  • 废弃生命周期: componentWillMount 挂载前 componentWillReceiveProps props改变就执行
              componentWillUpdate更新前
    

挂载阶段,挂载前通过constructor继承父类,获取props和state

1
2
3
4
5
6
7
constructor(props) {
super(props)
this.state = {
num: 0
}
console.log('constructor初始化组件时候执行一次,继承父类,获取props和state')
}

挂载阶段,挂载前的render,把jsx模板转化成vdom,多次调用

1
2
3
render() {
console.log('render 把jsx模板转化成vdom,多次调用')
}

挂载阶段:挂载后,dom节点渲染完毕再执行

1
2
3
componentDidMount() {
console.log('componentDidMount 挂载后,dom节点渲染完毕在执行')
}

更新阶段,同步父组件props到当前组件中的state

nextProps是最新的props,prevState是上一个状态的state

1
2
3
4
static getDerivedStateFromProps(nextProps, prevState) {
console.log('getDerivedStateFromProps 同步父组件props到当前组件中的state')
return null
}

更新阶段,shouldComponentUpdate简称SCU做性能优化,return true标识render会执行,false标识render不执行

1
2
3
4
shouldComponentUpdate(){
console.log('shouldComponentUpdate 做性能优化,return true标识render会执行,false标识render不执行');
return true
}

更新阶段,修改更新后

1
2
3
componentDidUpdate() {
console.log('componentDidUpdate 修改后')
}

销毁阶段,销毁前

1
2
3
componentWillUnmount() {
console.log('componentWillUnmount 销毁前')
}

数据发生改变,更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
handleClick = ()=>{
this.setState({num:this.state.num+1})
}

// jsx模板
render() {
console.log('render 把jsx模板转化成vdom,多次调用');
return (
<div>
<h3>生命周期</h3>
<p>{ this.state.num }</p>
<button onClick={ this.handleClick }>+1</button>
</div>
)
}

父子通信

父往子通信
父组件的数据动态属性绑定在子组件占位符,子组件通过props获取即可。

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
// 父组件
state = {
arr: [
{ id: 1, username: '小明' },
{ id: 2, username: '小洪' },
{ id: 3, username: '小李' }
]
}

// 父组件jsx模板
render() {
return (
<div>
<h3>父组件</h3>
<Child arr={this.state.arr} />
</div>
)
}

// 子组件jsx模板
render() {
return (
<div>
<h3>子组件</h3>
{
this.props.arr.map(item => <li key={item.id}>{item.username}</li>)
}
</div>
)
}

子往父通信
父组件把某个函数动态属性绑定到子组件的占位符,子组件通过props回调这个函数并传参即可。

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
// 父组件
addHandle = (val) => {
console.log('触发了', val)
this.setState({
arr: this.state.arr.concat({ // 注意concat参数可以是其它类型元素,不一定非是数组,同时它不影响原数组
id: new Date().getTime(),
username: val
})
})
}

// 父组件jsx模板
<Child arr={this.state.arr} addHandle={this.addHandle} />

// 子组件jsx模板
<input onKeyDown={this.handleClick} ref={username => this.username=username} type="text" placeholder='请输入用户名'/>
{
this.props.arr.map(item => <li key={item.id}>{item.username}</li>)
}

handleClick = (e) => {
if (e.keyCode === 13) { // 回车键
var val = this.username.value // 通过ref获取元素再取值
this.props.addHandle(val) // 通过props回调父组件传递的函数即可
}
}

跨层级通信

在实际的项目中,当需要组件间跨级访问信息时,如果还使用组件层层传递props,此时代码显得不那么优雅,甚至有些冗余。在react中,我们还可以使用context来实现跨级父子组件间的通信。

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据。

在React的Context中,数据我们可当成商品,发布数据的组件会用provider身份(卖方),接收数据的组件使用consumer身份(卖方)

步骤:

  1. 定义全局context,全局数据源
1
2
3
4
5
6
7
// 定义全局context,全局数据源创建一个context.js文件]
// import { createContext } from "react"
// export default createContext()

// 或者下面这种写法
import React from 'react'
export default React.createContext('1000') //创建全局context出来,全局数据载体,设置默认值
  1. 方案一:适用类组件和函数组件
    引入全局context到提供商品的组件

    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
    // 祖先->Child1->Child2
    // 祖先组件 [提供数据]
    import Child1 from './Child1' // 在上面,不能在context下面
    import context from "../context"
    // provider是用来给后代传递数据的,value就是传递的数据
    let { Provider } = context

    class ContextDemo extends Component {
    state = {
    num: 100, // 基础
    arr: [1,2,3,4,5] // 引用
    }
    render() {
    return (
    <div>
    <h3>跨层级通信</h3>
    {/* provider是用来给后代传递数据的,value就是传递的数据 */}
    <Provider value={this.state.num}> // 不能直接传递state
    {/* 子组件1 */}
    <Child1 />
    </Provider>
    </div>
    )
    }
    }

    // Child1 [不需要什么操作]
    class Child1 extends Component {
    render() {
    return (
    <div>
    <h3>Child1</h3>
    {/* 子组件2 */}
    <Child2 />
    </div>
    )
    }
    }

    // Child2 [购买数据] 需要context
    import context from "../context "
    // 后代获取祖先组件传递的值 Consumer获取祖先组件的数据
    let { Consumer } = context
    class Child2 extends Component {
    render() {
    return (
    <div>
    <h3>Child2</h3>
    <Consumer>
    {
    value => {
    return <div>获取到的数据:{value}</div>
    }
    }
    </Consumer>

    </div>
    )
    }
    }
  2. 方案二,只适用于类组件,通过this.context 拿到我们的值,可以在任意地方使用

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
// 祖先组件
import Child1 from './Child1'

// 引入context对象
import context from "../context"
let { Provider } = context

class ContextDemo extends Component {
state = {
num: 100,
arr: [1,2,3,4,5]
}
render() {
return (
<div>
<h3>跨层级通信</h3>
{/* Provider是用来给后代传递数据的,value就是传递的数据 */}
<Provider value={this.state}>
{/* 子组件1 */}
<Child1 />
</Provider>
</div>
)
}
}

// Child2 也需要context
import React, { Component } from 'react'
import context from "../context"

class Child2 extends Component {
//类组件可以通过静态的属性 contextType来接收全局的context数据
static contextType = context
render() {
return (
<div>
<h3>Child2</h3>
{/* 方案2 */}
<p>{this.context.arr}</p>
</div>
)
}
}
  1. 两者区别:方案一与方案二如果祖先提供this.state,那么方案一需要value.arr取值,方案二直接this.context就是arr;如果祖先提供this.state.arr,那么方案一value直接就是arr,方案二需要this.context.arr取值

Bus通信

bus通信:任何两个毫无相关的组件都可以使用bus通信来进行传递。

步骤:

  1. 安装插件npm i -S events,创建一个eventBus文件,定义全局事件中心并抛出

    1
    2
    3
    // 1.定义全局的事件中心  抛出
    import { EventEmitter } from 'events'
    export default new EventEmitter()
  2. 发送自定义事件的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 引入eventBus
import eventBus from '../eventBus'
class Child1 extends Component {
state = {
num: 100
}
handle = () => {
// 通过emit发送事件并携带数据
eventBus.emit('handleClick', this.state.num)
}
render() {
return (
<div>
<h3>Child1</h3>
<button onClick={this.handle}>点击</button>
</div>
)
}
}
  1. 添加自定义事件的组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import eventBus from '../eventBus'
class Child2 extends Component {
// 挂载后
componentDidMount() {
eventBus.addListener('handleClick', (val) => {
console.log('触发了', val) // 100
})
}
render() {
return (
<div>
<h3>Child2</h3>
</div>
)
}
}