简介:redux是专门用来做状态管理的Js库

第一章 redux理解

1.redux是什么

  1. redux是专门用来做状态管理的JS库
  2. 它可以在react/vue/Angular框架中使用
  3. 作用:集中式管理react应用中多个组件共享的状态

2.什么情况下需要使用redux

  1. 某个组件的状态需要共享给其他组件
  2. 一个组件需要改变另一个组件的状态(通信)

3.redux的安装

  • yarn add redux

第二章 redux三个核心概念

  • action:就是要操作的状态对象
    • type:要执行的动作
    • data:执行动作需要操作的数据
  • dispatch:分发,用于将action提交给其他地方,继续将action往下送给Store
  • Store:action的存储地方,不进行任何的状态加工
  • Reducer:用于加工action,与Store互动,并且可以初始状态

第三章 redux核心API

  • store.getState:获取store的状态
  • store.dispatch:将Action发送给store,Action也可以是一个里面有action对象的回调函数
  • store.subscribe:用于订阅每次改变的状态,并捕获状态的更新

第四章 使用redux编写简易应用

1.原生react编写:

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
import React, { Component } from 'react'

export default class Count extends Component {
state = {
count: 0
}
increment = () => {
const {count} = this.state
const {value} = this.selectNumber
this.setState({
// 这是强制类型转换
count: count + value * 1
})
}
incrementIfOdd = () => {
const {count} = this.state
const {value} = this.selectNumber
if((count + value * 1) % 2 === 1){
this.setState({
// 这是强制类型转换
count: count + value * 1
})
}
}
incrementIfAsync = () => {
const {count} = this.state
const {value} = this.selectNumber
setTimeout(() => {
this.setState({
// 这是强制类型转换
count: count + value * 1
})
}, 1000)
}
render() {
return (
<div>
<h1>当前页面为:{this.state.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
</select>
<br />
<button onClick={this.increment}>+</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementIfAsync}>异步加</button>
</div>
)
}
}

2.简易redux编写

  • 省略Action Creater
  • redux的状态更改不会直接导致页面渲染,需要自己重新渲染
  • redux的公共状态更新时,在组件中需要使用componentDidMount捕获,当然,最好的办法就是在index入口文件就进行捕获
  • reducer是一个纯函数,只用于根据type执行相应的操作而已

store文件

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

// 引入createStore,专门用于创建redux中最为核心的redux对象
import {createStore} from 'redux'

// 引入为Count组件服务的reducer
import countReducer from './count_reducer'

// 暴露store
export default createStore(countReducer)

reducer文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
* 2.reducer函数会接到两个参数:preState和action
* 3.没有action,代表此次reducer要初始化一个默认值,第一次的preState为undefined,第一次的action的type为乱码
*/
// 初始化状态
const initState = 100
export default function countReducer(preState = initState, action){
// 在action中拿到type和data
const {type, data} = action
switch (type) {
case 'increment':
return preState + data
case 'decrement':
return preState - data
default:
return preState
}
}

Count组件文件

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
import React, { Component } from 'react'
import store from '../../redux/store'

export default class Count extends Component {
// 我们需要的状态已经在redux使用了,共享的数据就不要出现在state,state就出现个人的数据就好
state = {
no_matters: true
}
/**
* 检测redux状态的变化
* 使用钩子函数,只要状态发生变化就会引起该钩子函数的发生
* 当然,可以在index入口文件书写,从而不书写该钩子函数
*/
// componentDidMount(){
// store.subscribe(()=>{
// // 在这里,setState不是作为更新状态使用,而是使用它的另一个特性:更新状态后重新渲染
// this.setState({})
// })
// }


increment = () => {
const {value} = this.selectNumber
// 获取action
var Action = {
type: 'increment',
data: value * 1
}
// 通知redux获取value到action中
store.dispatch(Action)
}
incrementIfOdd = () => {
const {value} = this.selectNumber
const Data = store.getState()
// 获取action
var Action = {
type: 'increment',
data: value * 1
}
// 通知redux获取value到action中
if((Data + Action.data) % 2 === 1){
store.dispatch(Action)
}
}
incrementIfAsync = () => {
const {value} = this.selectNumber
var Action = {
type: 'increment',
data: value * 1
}
setTimeout(() => {
store.dispatch(Action)
}, 1000)
}
render() {
return (
<div>
<h1>当前页面为:{store.getState()}</h1>
<select ref={c => this.selectNumber = c}>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
</select>
<br />
<button onClick={this.increment}>+</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementIfAsync}>异步加</button>
</div>
)
}
}

index文件

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'

// 第一次初始化
ReactDOM.render(<App/>,document.getElementById('root'))

// 这种书写就是redux的监测然后再从入口文件实时更新
// 好处:一次性将所有的状态一次性全部更新,减少状态改变导致的代码负担
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})

3.完整redux编写

  • 新增文件:

    • action.js:专门用于创建action对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import store from './store'

    export const actionIncreament = (value) => ({
    type: 'increment',
    data: value * 1
    })

    export const actionDecrement = (value) => ({
    type: 'decrement',
    data: value * 1
    })
    • constant.js:专门用于管理type字符串,防止程序员的单词拼写错误
    1
    2
    3
    4
    5
    6
    /**
    * 该模块是为了定义action变量中的常量值
    */

    const INCREMENT = 'increment'
    const DECREMENT = 'decrement'

第五章 redux异步编程

  • store文件
1
2
3
4
5
6
// 我们需要在store文件上面添加:
// 引入redux-thunk用于支持异步action
import thunk from 'redux-thunk'

// https://cloud.tencent.com/developer/section/1374199
export default createStore(countReducer, applyMiddleware(thunk))
  • action文件
1
2
3
4
5
6
7
8
// 异步action,就是指action的返回值是一个回调函数,因为函数能开启异步任务
export const actionAsynDecrement = (value, time) => {
return (dispatch) => {
setTimeout(()=>{
store.dispatch(actionIncreament(value))
},time)
}
}
  • 组件Count的index文件
1
2
3
4
5
6
7
8
9
10
11
12
incrementIfAsync = () => {
const {value} = this.selectNumber
// var Action = {
// type: 'increment',
// data: value * 1
// }
// setTimeout(() => {
// store.dispatch(Action)
// }, 1000)
// 异步action
store.dispatch(actionAsynDecrement(value, 1000))
}

按照这样的书写方式,为redux的action异步编程

善用store.dispatch(),该函数不仅可以接收对象,还可以接收回调函数

第六章 react-redux

1.react-redux和UI组件的关系

  • 所有的UI组件都应该包裹一个容器组件,他们是父子关系
  • 容器组件时真正跟redux打交道的,里面可以随意使用redux的api
  • UI组件中不能使用redux的api
  • 容器组件会传给UI组件:
    • redux保存的状态
    • 用于操作状态的方法
  • 传递状态和操作状态的方法,我们使用props
  • react-redux有不想让UI组件直接操作状态的意思

2.一些规范

  • UI组件都放在Component文件夹中
  • 容器组件都放在Container文件夹中

3.模型图

4.连接实例

  • 作为容器组件,与react-redux连接,那么容器组件需要大量使用react-redux的api

  • 连接方法

    App文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import React, { Component } from 'react'
    import Count from './containers/Count'
    import store from './redux/store'

    export default class App extends Component {
    render() {
    return (
    <div>
    <Count store={store}/>
    </div>
    )
    }
    }

    容器文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 引入UI组件
    import CountUI from '../../Component/Count'
    // // 引入redux的store
    // import store from '../../redux/store'
    // 引入react-redux中的connect方法
    import {connect} from 'react-redux'

    // 连接redux和UI组件
    const CountContainer = connect()(CountUI)

    export default CountContainer

5.数据互动

  • 原理:

    • 连接之后,connect(mapDispatchToProps, mapDispatchToProps)(UI),mapStateToProps与mapDispatchToProps都代表一个对象:
      • mapStateToProps函数返回的对象作为key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——状态
      • mapDispatchToProps函数返回的对象作为key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——操作状态的方法
    • 因此,我们只要将需要渲染的数据先在mapStateToProps拿出来,再从组件的DOM操作中使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export default connect(
    // mapStateToProps
    state => ({
    people:state.personReducer,
    countNum:state.countReducer
    }),
    // mapDispatchToProps
    {
    add: actionAddPeople //用于添加人
    }
    )(Person)
    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
    class Count extends Component {
    Syn = () => {
    const {value} = this.selectNumber
    this.props.addSyn(value)
    }
    Asyn = () => {
    const {value} = this.selectNumber
    this.props.addAsyn(value, 500)
    }

    render() {
    return (
    <div>
    <h1>count:{this.props.count}</h1>
    <h1>number of people:{this.props.peopleNum}</h1>
    <select ref={c => this.selectNumber = c}>
    <option value={1}>1</option>
    <option value={2}>2</option>
    <option value={3}>3</option>
    </select>
    <br />
    <button onClick={this.Syn}>Syn+</button>
    <button onClick={this.Asyn}>Asyn+</button>
    </div>
    )
    }

第七章 使用react-redux数据共享

1.注意事项

  • 我们需要将其他组件对应的reducer合并
  • 合并之后变成了一个对象,我们需要自行进行对象的取属性的 ‘’ . ‘’操作

2.原理与详细

  • 原理:

    • 连接之后,connect(mapDispatchToProps, mapDispatchToProps)(UI),mapStateToProps与mapDispatchToProps都代表一个对象:
      • mapStateToProps函数返回的对象作为key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——状态
      • mapDispatchToProps函数返回的对象作为key就作为传递给UI组件props的key,value就作为传递给UI组件props的value——操作状态的方法
    • 因此,我们只要将需要渲染的数据先在mapStateToProps拿出来,再从组件的DOM操作中使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export default connect(
    // mapStateToProps
    state => ({
    people:state.personReducer,
    countNum:state.countReducer
    }),
    // mapDispatchToProps
    {
    add: actionAddPeople //用于添加人
    }
    )(Person)
    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
    class Count extends Component {
    Syn = () => {
    const {value} = this.selectNumber
    this.props.addSyn(value)
    }
    Asyn = () => {
    const {value} = this.selectNumber
    this.props.addAsyn(value, 500)
    }

    render() {
    return (
    <div>
    <h1>count:{this.props.count}</h1>
    <h1>number of people:{this.props.peopleNum}</h1>
    <select ref={c => this.selectNumber = c}>
    <option value={1}>1</option>
    <option value={2}>2</option>
    <option value={3}>3</option>
    </select>
    <br />
    <button onClick={this.Syn}>Syn+</button>
    <button onClick={this.Asyn}>Asyn+</button>
    </div>
    )
    }
    }
  • store文件:将多个reducer同时合并起来

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createStore, applyMiddleware, combineReducers } from 'redux'
import CountReducer from './reducers/count_reducer'
import PersonReducer from './reducers/person_reducer'
import thunk from 'redux-thunk'

// 合并Reducers
const allReducers = combineReducers({
countReducer: CountReducer,
personReducer: PersonReducer
})

// 暴露store并使其能够异步操作
export default createStore(allReducers, applyMiddleware(thunk))
  • 入口文件:Provider的用处是重点
1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './redux/store'
import App from './App'

ReactDOM.render(
// 在这里Provider的作用就是同时包被App里面的组件并且精准传递改变的,并且自行需要的 状态
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
  • action文件夹

    • count_action
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import store from '../store'
    import {INCREMENT} from '../constant'

    export const actionIncrement = (value) => ({
    type: INCREMENT,
    data: value * 1
    })

    export const actionAsynIncrement = (value, time) => {
    return (dispatch) => {
    setTimeout(()=>{
    store.dispatch(actionIncrement(value))
    }, time)
    }
    }
    • person_action
    1
    2
    3
    4
    5
    6
    import {ADDPEOPLE} from '../constant'

    export const actionAddPeople = (people) => ({
    type: ADDPEOPLE,
    data: people
    })
  • reducer文件夹

    • count_reducer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import {INCREMENT, DECREMENT} from '../constant'

    const initState = 100
    export default function countReducer(preState = initState, action){
    const {type, data} = action
    switch(type){
    case INCREMENT:
    return preState + data
    case DECREMENT:
    return preState - data
    default:
    return preState
    }
    }
    • person_reducer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import {ADDPEOPLE} from '../constant'

    const initName = [
    {id: 1, name: 'kd', age: 12}
    ]
    export default function personReducer(preState = initName, action){
    const {type, data} = action
    switch(type){
    case ADDPEOPLE:
    return [data, ...preState]
    default:
    return preState
    }
    }
  • 组件文件夹:将容器组件和UI组件一起书写,所以放在一个文件夹里面了

    • Count
    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
    import React, { Component } from 'react'
    import {connect} from 'react-redux'
    import {actionIncrement, actionAsynIncrement} from '../../redux/actions/count_action'

    class Count extends Component {
    Syn = () => {
    const {value} = this.selectNumber
    this.props.addSyn(value)
    }
    Asyn = () => {
    const {value} = this.selectNumber
    this.props.addAsyn(value, 500)
    }

    render() {
    return (
    <div>
    <h1>count:{this.props.count}</h1>
    <h1>number of people:{this.props.peopleNum}</h1>
    <select ref={c => this.selectNumber = c}>
    <option value={1}>1</option>
    <option value={2}>2</option>
    <option value={3}>3</option>
    </select>
    <br />
    <button onClick={this.Syn}>Syn+</button>
    <button onClick={this.Asyn}>Asyn+</button>
    </div>
    )
    }
    }

    export default connect(
    state => ({
    count: state.countReducer,
    peopleNum: state.personReducer.length
    }),
    {
    addSyn: actionIncrement,
    addAsyn: actionAsynIncrement
    }
    )(Count)
    • Person
    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
    import React, { Component } from 'react'
    import {connect} from 'react-redux'
    import {actionAddPeople} from '../../redux/actions/person_action'
    import personReducer from '../../redux/reducers/person_reducer'
    import {nanoid} from 'nanoid'

    class Person extends Component {
    addPerson = () => {
    const name = this.nameNode.value
    const age = this.ageNode.value
    const Obj = { id:nanoid(), name, age }
    this.props.add(Obj)
    }
    render() {
    return (
    <div>
    <h1>the number of people:{this.props.number}</h1>
    <h1>the number of count:{this.props.countNum}</h1>
    <ul>
    {
    this.props.people.map((p)=>{
    return <li key={p.id}>{p.name}-----{p.age}</li>
    })
    }
    </ul>
    <input ref={c=>this.nameNode = c} type="text" placeholder="输入名字"/>
    <input ref={c=>this.ageNode = c} type="text" placeholder="输入年龄"/>
    <button onClick={this.addPerson}>添加</button>
    </div>
    )
    }
    }

    export default connect(
    state => ({
    people:state.personReducer,
    countNum:state.countReducer
    }),
    {
    add: actionAddPeople
    }
    )(Person)

第八章 纯函数

  • redux机制:发现preState和之后的状态一致的时候,就不会发生更新

    • 在更新的时候会发生一次浅比较,如果两者地址相同,就不会进行更新
    • 因此,对于数组操作:unshift(),push(),是不可以的
    • 因此我们需要[…preState,data]来进行新数组包装操作
  • 纯函数:

    • 只要是同样的输入,必定得到同样的相同的输出
    • 纯函数必须遵守以下内容:
      • 不得改写参数数据
      • 不会产生任何副作用,比如网络请求,和输入输出设备
      • 不能调用Date.now()或者Math.random()等不纯的方法
    • redux的reducer函数必须是一个纯函数

第九章 redux开发者工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { createStore, applyMiddleware, combineReducers } from 'redux'
import CountReducer from './reducers/count_reducer'
import PersonReducer from './reducers/person_reducer'
// 使用开发者工具
import {composeWithDevTools} from 'redux-devtools-extension'

import thunk from 'redux-thunk'

// 合并Reducers
const allReducers = combineReducers({
countReducer: CountReducer,
personReducer: PersonReducer
})

// 暴露store并使其能够异步操作
export default createStore(allReducers, composeWithDevTools(applyMiddleware(thunk)))

这样就可以进行开发者工具的使用了

第十章 打包运行

  • 输入指令:npm run build

  • 输入之后,会产生一个build文件夹,这个文件夹都是存粹的html和js

  • 接下来就是把文件部署到服务器上面

    • 用node手写服务器
    • 用serve服务(适用于自行查看效果)
      • 全局安装:npm i serve -g
      • 命令行输入:serve build:以build为入口打开