一. redux核心

1.1. redux入门

1.1.1 state

用于数据存储

1
2
3
const initialState = {
count: 0
}

1.1.2. action

通过action来更新state中的数据

  • 所有数据的变化,必须通过派发(dispatch)action来更新
  • action是一个普通的JavaScript对象,用来描述这次更新的type和content

优势与用法

  • 使用action可以清晰的知道数据所发生的变化,所有的数据变化都是可追踪、可预测的
  • 目前的action是固定的对象,实际开发中,会通过函数来定义,返回一个action
1
2
const addCounterAction = { type: "ADD_COUNTER", num: 6 }
const subCounterAction = { type: "SUB_COUNTER", num: 2 }

1.1.3. reducer

将state和action联系在一起

  • reducer是一个纯函数
  • reducer做的事情就是将传入的state和action结合起来生成一个新的state

1.1.4. redux三大原则

单一数据源

  • 整个应用程序的state被存储在一棵object tree中,并且这个object tree只存储在一个 store 中
  • redux并没有强制不能创建多个store,但是那样做并不利于数据的维护
  • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改

state是只读的

  • 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改state
  • 这样就确保了View或网络请求都不能直接修改state,只能通过action来修改state
  • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题

使用纯函数来执行修改

  • 通过reducer将旧state和actions联系在一起,并且返回一个新的state
  • 随着应用程序的复杂度增加,可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分
  • 但是所有的reducer都应该是纯函数,不能产生任何的副作用

1.2. redux基本使用

1.2.1. redux安装与配置

安装

1
npm install redux

ES6模块化配置

  • node v13.2.0之前,需要进行如下操作
    • 在package.json中添加属性: “type”: “module”
    • 在执行命令中添加如下选项:node –experimental-modules src/index.js
  • node v13.2.0之后,只需要进行如下操作
    • 在package.json中添加属性: “type”: “module”
  • 注意:导入文件时,需要跟上.js后缀名

1.2.2. redux使用过程

1.创建一个对象,作为要保存的状态

1
2
3
4
5
const { createStore } = require("redux")

const initialState = {
count: 0
}

2.创建store来存储这个state

  • 创建store时必须创建reducer
  • 通过 store.getState 来获取当前的state
1
2
3
4
5
6
7
const reducer = (state = initialState, action) => {
return state
}

const store = createStore(reducer)

const state = store.getState()

3.通过action来修改state

  • 通过dispatch来派发action
  • 通常action中都会有type属性,也可以携带其他的数据
1
2
3
4
5
const addCounterAction = { type: "ADD_COUNTER", num: 6 }
const subCounterAction = { type: "SUB_COUNTER", num: 2 }

store.dispatch(addCounterAction)
store.dispatch(subCounterAction)

4.修改reducer中的处理代码

  • reducer是一个纯函数,不需要直接修改state
1
2
3
4
5
6
7
8
9
10
const reducer = (state = initialState, action) => {
switch (action.type) {
case: "ADD_COUNTER":
return { ...state, count: state.count + action.num }
case: "SUB_COUNTER":
return { ...state, count: state.count - action.num }
default:
return state
}
}

5.在派发action之前,监听store的变化

1
2
3
4
store.subscribe(() => {
const state = store.getState()
console.log(state)
})

6.完整实现

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
const { createStore } = require("redux")

const initialState = {
count: 0
}

const reducer = (state = initialState, action) => {
switch (action.type) {
case: "ADD_COUNTER":
return { ...state, count: state.count + action.num }
case: "SUB_COUNTER":
return { ...state, count: state.count - action.num }
default:
return state
}
}

const store = createStore(reducer)

store.subscribe(() => {
const state = store.getState()
console.log(state)
})

const addCounterAction = { type: "ADD_COUNTER", num: 6 }
const subCounterAction = { type: "SUB_COUNTER", num: 2 }

store.dispatch(addCounterAction)
store.dispatch(subCounterAction)

1.2.3. redux结构划分

store/index.js文件

1
2
3
4
5
6
import { createStore } from "redux"
import reducer from "./reducer.js"

const store = createStore(reducer)

export default store

store/reducer.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { ADD_COUNTER, SUB_COUNTER } from "./constants.js"

const initialState = {
count: 0
}

const reducer = (state = initialState, action) => {
switch (action.type) {
case: ADD_COUNTER:
return { ...state, count: state.count + action.num }
case: SUB_COUNTER:
return { ...state, count: state.count - action.num }
default:
return state
}
}

export default reducer

store/actionCreators.js文件

1
2
3
4
5
6
7
8
9
import { ADD_COUNTER, SUB_COUNTER } from "./constants.js"

const addCounterAction = (num) => ({ type: ADD_COUNTER, num })
const subCounterAction = (num) => ({ type: SUB_COUNTER, num })

export {
addCounterAction,
subCounterAction
}

store/constants.js文件

1
2
3
4
5
6
7
const ADD_COUNTER = addCounter
const SUB_COUNTER = subCounter

export {
ADD_COUNTER,
SUB_COUNTER
}

1.2.4. redux流程解析

image-20230410095424177

image-20230410095601266

1.2.5. redux在react中的使用

views/Home.jsx

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

import store from '../store'
import {
addCounterAction,
subCounterAction
} from '../store/actionCreators'

export class Home extends PureComponent {
constructor(props) {
super(props)

this.state = {
count: store.getState().count
}
}

componentDidMount() {
store.subscribe(() => {
this.setState({
count: store.getState().count
})
})
}

addCounter(num) {
store.dispatch(addCounterAction(num))
}

subCounter(num) {
store.dispatch(subCounterAction(num))
}

render() {
const { count } = this.state
return (
<div>
<h1>Home</h1>
<h2>当前计数: {count}</h2>
<button onClick={e => this.addCounter(2)}>+2</button>
<button onClick={e => this.subCounter(1)}>-1</button>
</div>
)
}
}

export default Home

1.3. react-redux

安装

1
npm install react-redux

connect函数

views/Home.jsx

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
import React, { PureComponent } from 'react'
import { connect } from "react-redux"
import {
addCounterAction,
subCounterAction
} from '../store/actionCreators'

export class Home extends PureComponent {
addCounter(num) {
const { addCounter } = this.props
addCounter(num)
}

subCounter(num) {
const { subCounter } = this.props
subCounter(num)
}

render() {
const { count } = this.props
return (
<div>
<h1>Home</h1>
<h2>当前计数: {count}</h2>
<button onClick={e => this.addCounter(2)}>+2</button>
<button onClick={e => this.subCounter(1)}>-1</button>
</div>
)
}
}

const mapStateToProps = (state) => ({count: state.count})

const mapDispatchToProps = (dispatch) => ({
addCounter: (num) => dispatch(addCounterAction(num))
subCounter: (num) => dispatch(subCounterAction(num))
})

export default connect(mapStateToProps, mapDispatchToProps)(Home)

Provider

  • 注意:这里传入的是store属性,而不是value属性

src/index.js

1
2
3
4
5
6
7
8
9
10
11
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App'
import store from './store'

const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(
<Provider store={store}>
<App />
</Provider>
)

1.4. redux异步操作

注意:以下示例代码,使用react-redux处理,并省略Provide的配置

1.4.1. redux配置

reducer.js

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
import { 
...,
FETCH_BANNERS,
FETCH_RECOMMENDS
} from "./constants.js"

const initialState = {
...,
banners: [],
recommends: []
}

const reducer = (state = initialState, action) => {
switch (action.type) {
...
case: FETCH_BANNERS:
return { ...state, banners: action.data }
case: FETCH_RECOMMENDS:
return { ...state, recommends: action.data }
default:
return state
}
}

export default reducer

store/actionCreators.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { 
...,
FETCH_BANNERS,
FETCH_RECOMMENDS
} from "./constants.js"

...
const fetchBannersAction = (data) => ({ type: FETCH_BANNERS, data })
const fetchRecommendsAction = (data) => ({ type: FETCH_RECOMMENDS, data })

export {
...,
fetchBannersAction,
fetchRecommendsAction
}

store/constants.js文件

1
2
3
4
5
6
7
8
9
...
const FETCH_BANNERS = fetchBanners
const FETCH_RECOMMENDS = fetchRecommends

export {
...,
FETCH_BANNERS,
FETCH_RECOMMENDS
}

1.4.2. 在组件中请求数据

views/Detail

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
import React, { PureComponent } from 'react'
import { connect } from "react-redux"
import axios from "axios"
import {
fetchBannersAction,
fetchRecommendsAction
} from '../store/actionCreators'

export class Detail extends PureComponent {
componentDidMount() {
const { fetchBanners, fetchRecommends} = this.props

axios.get("url").then(res => {
const banners = res.data.data.banner.list
const recommends = res.data.recommend.list

fetchBanners(banners)
fetchRecommends(recommends)
}).catch(err => {
console.log(err)
})
}

render() {
const { banners, recommends } = this.props
return (
<div>
<h1>Datail</h1>
<h2>banners: </h2>
<div>
{
banners.map(item => <div key={item}>{item}</div>)
}
</div>
<h2>recommends: </h2>
<div>
{
recommends.map(item => <div key={item}>{item}</div>)
}
</div>
</div>
)
}
}

const mapStateToProps = (state) => ({
banners: state.banners,
recommends: state.recommends
})

const mapDispatchToProps = (dispatch) => ({
fetchBanners: (data) => dispatch(fetchBannersAction(data)),
fetchRecommends: (data) => dispatch(fetchRecommendsAction(data))
})

export default connect(mapStateToProps, mapDispatchToProps)(Detail)

1.4.3. 在action中请求数据

1.4.3.1. 中间件

redux也引入了中间件(Middleware)的概念

  • 中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码
  • 比如日志记录、调用异步接口、添加代码调试功能等等
  • 官网推荐的中间件是使用 redux-thunk

redux-thunk发送异步请求原理

  • 默认情况下的dispatch(action),action需要是一个JavaScript的对象

  • redux-thunk可以让dispatch(action函数),action可以是一个函数

  • 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数

    • dispatch函数用于我们之后再次派发action

    • getState函数考虑到之后的一些操作需要依赖原来的状态,可以获取之前的一些状态

异步处理流程

image-20230410115636366

1.4.3.2. redux-thunk

redux-thunk的安装

1
npm install redux-thunk

使用过程

  • 在创建store时传入应用了middleware的enhance函数
    • 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
    • 将enhancer作为第二个参数传入到createStore中
1
2
3
4
5
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"

const enhancer = applyMiddleware(thunk)
const store = createStore(reducer, enhancer)
  • 定义返回一个函数的action
    • 注意:这里不是返回一个对象了,而是一个函数
    • 该函数在dispatch之后会被执行
1
2
3
4
5
6
7
8
9
10
11
const getDataAction = () => {
return (dispatch) => {
axios.get("url").then(res => {
const banners = res.data.data.banner.list
const recommends = res.data.recommend.list

dispatch(fetchBanners(banners))
dispatch(fetchRecommends(recommends))
})
}
}
1.4.3.3. 完整实现

store/index.js文件

1
2
3
4
5
6
7
8
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
import reducer from "./reducer.js"

const enhancer = applyMiddleware(thunk)
const store = createStore(reducer, enhancer)

export default store

store/actionCreators.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
const fetchDataAction = () => {
return (dispatch) => {
axios.get("url").then(res => {
const banners = res.data.data.banner.list
const recommends = res.data.recommend.list

dispatch(fetchBanners(banners))
dispatch(fetchRecommends(recommends))
}).catch(err => {
console.log(err)
})
}
}

export {
...,
fetchDataAction
}

views/Detail.jsx

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
import React, { PureComponent } from 'react'
import { connect } from "react-redux"
import {
fetchDataAction
} from '../store/actionCreators'

export class Detail extends PureComponent {
componentDidMount() {
const { fetchData } = this.props
fetchData()
}

render() {
const { banners, recommends } = this.props
return (
<div>
<h1>Datail</h1>
<h2>banners: </h2>
<div>
{
banners.map(item => <div key={item}>{item}</div>)
}
</div>
<h2>recommends: </h2>
<div>
{
recommends.map(item => <div key={item}>{item}</div>)
}
</div>
</div>
)
}
}

const mapStateToProps = (state) => ({
banners: state.banners,
recommends: state.recommends
})

const mapDispatchToProps = (dispatch) => ({
fetchData: () => dispatch(fetchBannersAction())
})

export default connect(mapStateToProps, mapDispatchToProps)(Detail)

1.5. redux-devtool

配置store/index.js

1
2
3
4
5
6
7
8
9
10
11
import { createStore, applyMiddleware, compose } from "redux"
import thunk from "redux-thunk"
import reducer from "./reducer.js"

const middlewareEnhancer = applyMiddleware(thunk)

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose

const store = createStore(reducer, composeEnhancers(middlewareEnhancer))

export default store

1.6. reducer模块拆分

counter模块: store/counter/index.js

1
2
3
import reducer from "./reducer.js"

export default reducer

remoteData模块: store/remote-data/index.js

1
2
3
import reducer from "./reducer.js"

export default reducer

出口文件: store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createStore, applyMiddleware, compose, combineReducers } from "redux"
import thunk from "redux-thunk"
import counterReducer from "./counter"
import remoteDataReducer from "./remote-data"

//会把counter, remoteData 分别作为一个模块, 获取state的数据时,需要先找到对应的模块,再在该模块中找到state中的数据
const reducer = combineReducers({
counter: counterReducer,
remoteData: remoteDataReducer
})

const middlewareEnhancer = applyMiddleware(thunk)

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose

const store = createStore(reducer, composeEnhancers(middlewareEnhancer))

export default store

state的使用: views/Home.jsx

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
import React, { PureComponent } from 'react'
import { connect } from "react-redux"
import {
addCounterAction,
subCounterAction
} from '../store/counter/actionCreators'
import store from "../store"

export class Home extends PureComponent {
constructor(){
super()
this.state = {
count: store.getState().counter // 此处也是需要找到对应模块的数据
}
}

addCounter(num) {
const { addCounter } = this.props
addCounter(num)
}

subCounter(num) {
const { subCounter } = this.props
subCounter(num)
}

render() {
const { count } = this.props
return (
<div>
<h1>Home</h1>
<h2>当前计数: {count}</h2>
<button onClick={e => this.addCounter(2)}>+2</button>
<button onClick={e => this.subCounter(1)}>-1</button>
</div>
)
}
}

// state在使用时,需要先找到对应的模块,再去找该模块中的数据
const mapStateToProps = (state) => ({count: state.counter.count})

// dispatch(action) 正常使用,没有区别
const mapDispatchToProps = (dispatch) => ({
addCounter: (num) => dispatch(addCounterAction(num))
subCounter: (num) => dispatch(subCounterAction(num))
})

export default connect(mapStateToProps, mapDispatchToProps)(Home)

二. redux进阶

2.1. redux-toolkit

2.1.1. redux-toolkit基本使用

1.安装redux-toolkit

1
npm install @reduxjs/toolkit react-redux

2.核心API

  • configureStore:包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合 slice reducer添加提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension
  • createSlice:接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions;createSlice返回值是一个对象,包含所有的actions;具有以下几个重要的参数:
    • name:用户标记slice的名词
      • 在之后的redux-devtool中会显示对应的名词
    • initialState:初始化值
      • 第一次初始化时的值
    • reducers:相当于之前的reducer函数
      • 对象类型,并且可以添加很多的函数
      • 函数类似于redux原来reducer中的一个case语句
      • 函数的参数
        • 参数一:state
        • 参数二:调用这个action时,传递的action参数
  • createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk

3.使用过程

store/counter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { createSlice } from "@reduxjs/toolkit"

const counterSlice = createSlice({
name: "counter",
initialState: {
count: 66
},
reducers: {
addCounter(state, action) {
console.log("add", state, action)
state.count += action.payload
},
subCounter(state, action) {
console.log("sub", state, action)
state.count -= action.payload
}
}
})

export default counterSlice.reducer

export const { addCounter, subCounter } = counterSlice.actions

store/index.js

1
2
3
4
5
6
7
8
9
10
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "./counter"

const store = configureStore({
reducer: {
counter: counterReducer
}
})

export default store

views/Home.jsx

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
import React, { PureComponent } from 'react'
import { connect } from "react-redux"
import store from '../store'
import {
addCounter,
subCounter
} from '../store/counter.js'

export class Home extends PureComponent {

addCounter(num) {
const { addCounter } = this.props
addCounter(num)
}

subCounter(num) {
const { subCounter } = this.props
subCounter(num)
}

render() {
const { count } = this.props
return (
<div>
<h1>Home</h1>
<h2>当前计数: {count}</h2>
<button onClick={() => this.addCounter(1)}>+1</button>
<button onClick={() => this.subCounter(1)}>-1</button>
</div>
)
}
}

const mapStateToProps = (state) => ({count: state.counter.count})

const mapDispatchToProps = (dispatch) => ({
addCount: (num) => dispatch(addCount(num)),
subCount: (num) => dispatch(subCount(num))
})

export default connect(mapStateToProps, mapDispatchToProps)(Home)

2.1.2. redux-toolkit异步操作

方式一: 在组件中请求数据

views/Detail.jsx

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
import React, { PureComponent } from 'react'
import { connect } from "react-redux"
import axios from "axios"
import {
fetchBanners,
fetchRecommends
} from '../store/remoteData.js'

export class Detail extends PureComponent {
componentDidMount() {
const { fetchBanners, fetchRecommends} = this.props

axios.get("url").then(res => {
const banners = res.data.data.banner.list
const recommends = res.data.recommend.list

fetchBanners(banners)
fetchRecommends(recommends)
}).catch(err => {
console.log(err)
})
}

render() {
const { banners, recommends } = this.props
return (
<div>
{/** ...对banners和recommends的展示 */}
</div>
)
}
}

const mapStateToProps = (state) => ({
banners: state.banners,
recommends: state.recommends
})

const mapDispatchToProps = (dispatch) => ({
fetchBanners: (data) => dispatch(fetchBanners(data))
fetchRecommends: (data) => dispatch(fetchRecommends(data))
})

export default connect(mapStateToProps, mapDispatchToProps)(Detail)

方式二: 在createAsyncThunk请求,使用extraReducers的对象类型处理

store/remoteData.js

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
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const fetchDataAction = createAsyncThunk("fetchData", async () => {
const res = await axios.get("url")
return res.data.data
})

const remoteDataSlice = createSlice({
name: "remoteData",
initialState: {
banners: [],
recommends: []
},
reducers: {
fetchBanners(state, { payload }) {
state.banners = payload
},
fetchRecommends(state, { payload }) {
state.recommends = payload
}
},
extraReducers: {
[fetchDataAction.pending]: (state, action) => {
const { type, payload } = action
console.log("pending", type, payload)
},
[fetchDataAction.fulfilled]: (state, { payload }) => {
state.banners = payload.banner.list
state.recommends = payload.recommend.list
},
[fetchDataAction.rejected]: (state, action) => {
console.log("rejected")
}
}
})

export default remoteDataSlice.reducer

export const { fetchBanners, fetchRecommends } = remoteDataSlice.actions
export fetchDataAction

方式三: 在createAsyncThunk请求,使用extraReducers的函数类型处理

store/remoteData.js

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
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const fetchDataAction = createAsyncThunk("fetchData", async () => {
const res = await axios.get("url")
return res.data.data
})

const remoteDataSlice = createSlice({
...,
extraReducers: builder => {
builder
.addCase(fetchDataAction.pending, state => {
console.log("pending")
})
.addCase(fetchDataAction.fulfilled, (state, { payload }) => {
state.banners = payload.banner.list
state.recommends = payload.recommend.list
})
.addCase(fetchDataAction.rejected, () => {
console.log("rejected")
})
}
})

export default remoteDataSlice.reducer

export const { fetchBanners, fetchRecommends } = remoteDataSlice.actions
export fetchDataAction

方式四: 在createAsyncThunk请求,使用回调函数的参数dispatch派发action

store/remoteData.js

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
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"
import axios from "axios"

const fetchDataAction = createAsyncThunk(
"fetchData",
async (payload, { dispatch, getState }) => {
const res = await axios.get("url")
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
dispatch(fetchBanners(banners))
dispatch(fetchRecommends(recommends))
})

const remoteDataSlice = createSlice({
name: "remoteData",
initialState: {
banners: [],
recommends: []
},
reducers: {
fetchBanners(state, { payload }) {
state.banners = payload
},
fetchRecommends(state, { payload }) {
state.recommends = payload
}
}
})

export default remoteDataSlice.reducer

export const { fetchBanners, fetchRecommends } = remoteDataSlice.actions
export fetchDataAction

2.2. 原理实现

2.2.1. combineReducers

1
2
3
4
5
6
7
8
9
// combineReducers实现原理
function reducer(state = {}, action) {
// 返回一个对象, store的state
return {
counter: counterReducer(state.counter, action),
home: homeReducer(state.home, action),
user: userReducer(state.user, action)
}
}

2.2.2. react-redux

context

src/hoc/store_context.js

1
2
3
import { createContext } from "react";

export const StoreContext = createContext()

provider

src/App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { PureComponent } from 'react'
import store from './store'
import Home from './views/Home.jsx'
import { StoreContext } from './hoc/store_context.js'

export class APP extends PureComponent {
render() {
return (
<StoreContext.Provider value={store}>
<Home />
</StoreContext.Provider>
)
}
}

export default App

connect

src/hoc/connect.js

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
import { PureComponent } from "react"
import { StoreContext } from "./store_context.js"

export default function connect(mapStateToProps, mapDispatchToProps) {
return (WrapperComponent) => {
class ConnectCom extends PureComponent {
constructor(props, context) {
super(props)
console.log(context);
this.state = mapStateToProps(context.getState())
}

componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState({ ...mapStateToProps(this.context.getState()) })
})
}

componentWillUnmount() {
this.unsubscribe()
}

render() {
const stateProps = mapStateToProps(this.context.getState())
const dispatchProps = mapDispatchToProps(this.context.dispatch)

return <WrapperComponent {...this.props} {...stateProps} {...dispatchProps} />
}
}

ConnectCom.contextType = StoreContext

return ConnectCom
}
}

2.2.3. middleware

2.3.3.1. printLog
1
2
3
4
5
6
7
8
9
10
11
12
13
function printLog(store) {
const next = store.dispatch

function logAndDispatch(action) {
console.log("当前派发的action:", action)
// 真正派发的代码: 使用之前的dispatch进行派发
next(action)
console.log("派发之后的结果:", store.getState())
}

// monkey patch: 猴补丁 => 篡改现有的代码, 对整体的执行逻辑进行修改
store.dispatch = logAndDispatch
}
2.3.3.2. thunk
1
2
3
4
5
6
7
8
9
10
11
function thunk(store) {
const next = store.dispatch
function dispatchThunk(action) {
if (typeof action === "function") {
action(store.dispatch, store.getState)
} else {
next(action)
}
}
store.dispatch = dispatchThunk
}
2.3.3.3. applyMiddleware
1
2
3
4
5
6
function applyMiddleware(store, ...middlewares) {
middlewares.forEach(middleware => {
middleware(store)
})
}