1.Redux应用场景
在react中,数据在组件中单向流动的,数据只能从父组件向子组件流通(通过props),而两个非父子关系的组件之间通信就比较麻烦,redux的出现就是为了解决这个问题,它将组件之间需要共享的数据存储在一个store里面,其他需要这些数据的组件通过订阅的方式来刷新自己的视图。
2.Redux设计思想
它将整个应用状态存储到store里面,组件可以派发(dispatch)修改数据(state)的行为(action)给store,store内部修改之后,其他组件可以通过订阅(subscribe)中的状态state来刷新(render)自己的视图。
画图举栗子:
组件B,C需要根据组件A的数据来修改自己的视图,组件A不能直接通知组件BC,这个时候就可以将数据存储到sore里面,然后A组件向store里面的处理函数派发指令,reducer接收到指令action后修改state数据,组件BC可以通过订阅来修改自己的视图。
3.Redux应用的三大原则
- 单一数据源
我们可以把Redux的状态管理理解成一个全局对象,那么这个全局对象是唯一的,所有的状态都在全局对象store下进行统一”配置”,这样做也是为了做统一管理,便于调试与维护。 - State是只读的
与React的setState相似,直接改变组件的state是不会触发render进行渲染组件的。同样,在Redux中唯一改变state的方法就是触发action,action是一个用于描述发生了什么的“关键词”,而具体使action在state上更新生效的是reducer,用来描述事件发生的详细过程,reducer充当了发起一个action连接到state的桥梁。这样做的好处是当开发者试图去修改状态时,Redux会记录这个动作是什么类型的、具体完成了什么功能等(更新、传播过程),在调试阶段可以为开发者提供完整的数据流路径。 - Reducer必须是一个纯函数
Reducer用来描述action如何改变state,接收旧的state和action,返回新的state。Reducer内部的执行操作必须是无副作用的,不能对state进行直接修改,当状态发生变化时,需要返回一个全新的对象代表新的state。这样做的好处是,状态的更新是可预测的,另外,这与Redux的比较分发机制相关,阅读Redux判断状态更新的源码部分(combineReducers),发现Redux是对新旧state直接用==来进行比较,也就是浅比较,如果我们直接在state对象上进行修改,那么state所分配的内存地址其实是没有变化的,“==”是比较对象间的内存地址,因此Redux将不会响应我们的更新。之所以这样处理是避免对象深层次比较所带来的性能损耗(需要递归遍历比较)。
4.源码实现:
4.1 creatStore
export default function createStore(reducrer,initialState){
let state = initialState //状态
let listeners = []
//获取当前状态
function getState() {
return state
}
//派发修改指令给reducer
function dispatch(action) {
//reducer修改之后返回新的state
state = reducrer(state,action)
//执行所有的监听函数
listeners.forEach(listener => listener())
}
//订阅 状态state变化之后需要执行的监听函数
function subscribe(listener) {
listeners.push(listener) //监听事件
return function () {
let index = listeners.indexOf(listener)
listeners.splice(index,1)
}
}
//在仓库创建完成之后会先派发一次动作,目的是给初始化状态赋值
dispatch({type:'@@REDUX_INIT'})
return {
getState,
dispatch,
subscribe
}
}
解释一下上面代码:
仓库store里面存储着状态state和处理器reducer,这两个参数都是需要外界提供的,当外界想要修改这个state的时候需要通过调用dispatch方法派发action给reducer,reducer会根据action修改state,返回修改之后的值。并且执行组件订阅的监听事件函数。
看一个具体的栗子:
html代码:
<body>
<div id="root"></div>
<button id="increment">+</button>
<button id="decrement">-</button>
</body>
js代码:
import {createStore} from './redux'
const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'
let initailState = {number:0}
//处理函数
function reducer(state = initailState,action) {
switch(action.type) {
case INCREMENT:
return {number: state.number + 1}
case DECREMENT:
return {number: state.number - 1}
default:
return state;
}
}
//创建仓库
let store = createStore(reducer)
let root = document.getElementById('root')
let increment = document.getElementById('increment')
let decrement = document.getElementById('decrement')
//订阅
store.subscribe( () => {
root.innerHTML = store.getState().number
})
//点击的时候派发修改指令action
increment.addEventListener('click', () => {
store.dispatch({type: INCREMENT})
})
decrement.addEventListener('click', () => {
store.dispatch({type:DECREMENT})
})
点击 + 加一,点击 - 减一。
1.先创建好处理函数reducer(),告诉如何修改state,当派发的指令dispatch(),当action type = INCREMENT 的时候加一,当派发的指令 action type = DECREMENT的时候减一;
2.外界订阅store.subscribe():当状态发生变化之后修改视图界面 root.innerHTML = store.getState().number 。
函数组件和类组件使用对比:
action_type.js
export const ADD = 'ADD'
export const MINUS = 'MINUS'
reducer.js
import * as TYPES from './actions_type'
let initialState = {number: 0}
export default function reducer (state = initialState, action) {
switch (action.type) {
case TYPES.ADD:
return {number: state.number + 1}
case TYPES.MINUS:
return {number: state.number - 1}
default:
return state
}
}
store.js
import {createStore} from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store
组件Couter.js
import React, {useState,useEffect} from 'react'
import store from '../store'
import * as TYPES from '../store/actions_type'
//类组件写法:
export default class Couter extends React.Component {
state = {number: store.getState().number}
componentDidMount() {
//当状态发生变化后会让订阅函数执行,会更新当前组件状态,状态更新之后就会刷新组件
this.unSubscribe = store.subscribe( () => {
this.setState({number: store.getState().number})
})
}
//组件销毁的时候取消监听函数
componentWillUnmount() {
this.unSubscribe()
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={()=> store.dispatch({type:TYPES.ADD})}>+</button>
<button onClick={()=> store.dispatch({type:TYPES.MINUS})}>-</button>
</div>
)
}
}
//函数组件写法:
export default function Couter (props) {
let [number,setNumber] = useState(store.getState().number)
//订阅
useEffect( () => {
return store.subscribe( () => { //这个函数会返回一个销毁函数,此销毁函数会自动在组件销毁的时候调用
setNumber(store.getState().number)
})
},[]) //useEffect的第二个参数是依赖变量的数组,当这个依赖数组发生变化的时候才会执行函数
return (
<div>
<p>{store.getState().number}</p>
<button onClick={()=> store.dispatch({type:TYPES.ADD})}>+</button>
<button onClick={()=> store.dispatch({type:TYPES.MINUS})}>-</button>
</div>
)
}
/**
* 对于组件来说仓库有两个作用
* 1.输出:把仓库中的状态在组件中显示
* 2.输入:在组件里可以派发动作给仓库,从而修改仓库中的状态
* 3.组件需要订阅状态变化事件,当仓库中的状态发生改变之后需要刷新组件
*/
对于Couter组件分别用函数组件的方式和类组件的方式实现,他们最大区别在于订阅功能的实现,类组件有自己的状态state,可以通过setState方法修改状态。但是函数组件并没有状态,要想在函数组件中使用状态就需要通过hooks来实现。
useState这个hooks可以实现状态。number代表当前的状态值,setNumber代表改变状态的方法。
useEffect这个hooks可以用于处理组件中的effect,通常用于请求数据,事件处理,订阅等相关操作。
4.2 bindActionCreators
用法:
对以上栗子改写使用bindActionCreators:
actions_type.js
function add() {
return {type:TYPES.ADD}
}
function minus() {
return {type:TYPES.MINUS}
}
export default {
add,
minus
}
Counter.js
import React, {useState,useEffect} from 'react'
import store from '../store'
import actions from '../store/actions_type'
import { bindActionCreators } from 'redux'
let boundActions = bindActionCreators(actions, store.dispatch)
//类组件
export default class Couter extends React.Component {
state = {number: store.getState().number}
componentDidMount() {
//当状态发生变化后会让订阅函数执行,会更新当前组件状态,状态更新之后就会刷新组件
this.unSubscribe = store.subscribe( () => {
this.setState({number: store.getState().number})
})
}
//组件销毁的时候取消监听函数
componentWillUnmount() {
this.unSubscribe()
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={boundActions.add}>+</button>
<button onClick={boundActions.minus}>-</button>
</div>
)
}
}
源码实现:(简单版本)
export default function (actionCreators,dispatch) {
let boundActionsCreators = {}
//循环遍历重写action
for(let key in actionCreators) {
boundActionsCreators[key] = function(...args) {
//其实dispatch方法会返回派发的action
return dispatch(actionCreators[key](...args))
}
}
return boundActionsCreators
}
可以看到
完整版本的源码:
/**
参数说明:
actionCreators: action create函数,可以是一个单函数,也可以是一个对象,这个对象的所有元素都是action create函数
dispatch: store.dispatch方法
*/
export default function bindActionCreators(actionCreators, dispatch) {
// 如果actionCreators是一个函数的话,就调用bindActionCreator方法对action create函数和dispatch进行绑定
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// actionCreators必须是函数或者对象中的一种,且不能是null
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
// 获取所有action create函数的名字
const keys = Object.keys(actionCreators)
// 保存dispatch和action create函数进行绑定之后的集合
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
// 排除值不是函数的action create
if (typeof actionCreator === 'function') {
// 进行绑定
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
// 返回绑定之后的对象
/**
boundActionCreators的基本形式就是
{
actionCreator: function() {dispatch(actionCreator.apply(this, arguments))}
}
*/
return boundActionCreators
}
4.3combineReducer
combineReducer就是用来合并reducer的。
看个栗子:
两个组件:counter1和counter2
actions_type.js
export const ADD = 'ADD'
export const MINUS = 'MINUS'
export const ADD1 = 'ADD1'
export const MINUS1 = 'MINUS1'
export const ADD2 = 'ADD2'
export const MINUS2 = 'MINUS2'
actions动作Counter1.js
import * as TYPES from '../actions_type'
export default {
add() {
return {type: TYPES.ADD1}
},
minus() {
return {type: TYPES.MINUS1}
}
}
actions动作Counter2.js
import * as TYPES from '../actions_type'
export default {
add() {
return {type: TYPES.ADD2}
},
minus() {
return {type: TYPES.MINUS2}
}
}
reducer Counter1.js
import * as TYPES from '../actions_type'
let initialState = {number: 0}
export default function reducer (state = initialState, action) {
switch (action.type) {
case TYPES.ADD1:
return {number: state.number + 1}
case TYPES.MINUS1:
return {number: state.number - 1}
default:
return state
}
}
reducer Counter2.js
import * as TYPES from '../actions_type'
let initialState = {number: 0}
export default function reducer (state = initialState, action) {
switch (action.type) {
case TYPES.ADD2:
return {number: state.number + 1}
case TYPES.MINUS2:
return {number: state.number - 1}
default:
return state
}
}
合并后的reducer index.js
import {combineReducers} from '../../redux'
import counter1 from './Counter1'
import counter2 from './Counter2'
let reducers = {
counter1:counter1,
counter2:counter2
}
//合并
let combinedReducer = combineReducers(reducers)
export default combinedReducer
组件Counter1.js
import React, {useState,useEffect} from 'react'
import store from '../store'
// import actions from '../store/actions_type'
import { bindActionCreators } from 'redux'
import actions from '../store/actions/Counter1'
let boundActions = bindActionCreators(actions, store.dispatch)
//函数组件
export default function Couter (props) {
let [number,setNumber] = useState(store.getState().counter1.number)
//订阅
useEffect( () => {
return store.subscribe( () => { //这个函数会返回一个销毁函数,此销毁函数会自动在组件销毁的时候调用
setNumber(store.getState().counter1.number)
})
},[]) //useEffect的第二个参数是依赖变量的数组,当这个依赖数组发生变化的时候才会执行函数
return (
<div>
<p>{number}</p>
<button onClick={boundActions.add}>+</button>
<button onClick={boundActions.minus}>-</button>
</div>
)
}
组件 Counter2.js
import React, {useState,useEffect} from 'react'
import store from '../store'
// import actions from '../store/actions_type'
import { bindActionCreators } from 'redux'
import actions from '../store/actions/Counter2'
let boundActions = bindActionCreators(actions, store.dispatch)
//函数组件
export default function Couter (props) {
let [number,setNumber] = useState(store.getState().counter2.number)
//订阅
useEffect( () => {
return store.subscribe( () => { //这个函数会返回一个销毁函数,此销毁函数会自动在组件销毁的时候调用
setNumber(store.getState().counter2.number)
})
},[]) //useEffect的第二个参数是依赖变量的数组,当这个依赖数组发生变化的时候才会执行函数
return (
<div>
<p>{number}</p>
<button onClick={boundActions.add}>+</button>
<button onClick={boundActions.minus}>-</button>
</div>
)
}
/**
* 合并rreducer
* 1.拿到子reducer,然后合并成一个reducer
* @param {*} state
* @param {*} action
*/
export default function combineReducers(reducers) {
//state是合并后得state = {counter1:{number:0},counter2:{number:0}}
return function (state={}, action) {
let nextState = {}
// debugger
for(let key in reducers) {
let reducerForKey = reducers[key] //key = counter1,
//老状态
let previousStateForKey = state[key] //{number:0}
let nextStateForKey = reducerForKey(previousStateForKey,action) //执行reducer,返回新得状态
nextState[key] = nextStateForKey //{number: 1}
}
return nextState
}
}
combineReducers 函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore。
合并后的 reducer 可以调用各个子 reducer,并把它们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。
4.4 redux connect
上面得栗子中每个组件要向使用store必须是每个都自己引入,有点麻烦,react提供了一个对象Provider和connect,可以通过这两个对象把react组件和store连接起来,不必再每个都引入。如果想在某个子组件中使用Redux维护的store数据,它必须是包裹在Provider中并且被connect过的组件,Provider的作用类似于提供一个大容器,将组件和Redux进行关联,在这个基础上,connect再进行store的传递。
如下:
import React from 'react'
import ReactDOM from 'react-dom'
import Couter1 from './components/Counter1'
import Couter2 from './components/Counter2'
import {Provider} from './react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<Couter1 /><Couter2 />
</Provider>,document.getElementById('root'))
我们想要在Counter1和Counter2中使用store中得数据,就需要把他们包裹在Provider中, store会作为Provider的属性props,以上下文的形式传递给下层组件。下层组件要想获取store,不需要再自己引入了,直接从上下文中取就可以了
import React from 'react'
import {creatContext} from 'react'
let ReactReduxContext = React.createContext() //创建上下文
export default ReactReduxContext
export default function Couter (props) {
let {store} = useContext(ReactReduxContext) //从上下文中拿到store
let [number,setNumber] = useState(store.getState().counter1.number)
//订阅
useEffect( () => {
return store.subscribe( () => { //这个函数会返回一个销毁函数,此销毁函数会自动在组件销毁的时候调用
setNumber(store.getState().counter1.number)
})
},[]) //useEffect的第二个参数是依赖变量的数组,当这个依赖数组发生变化的时候才会执行函数
return (
<div>
<p>{number}</p>
<button >+</button>
<button >-</button>
</div>
)
}
Counter2.js
export default function Couter (props) {
let {store} = useContext(ReactReduxContext)
let [number,setNumber] = useState(store.getState().counter2.number)
//订阅
useEffect( () => {
return store.subscribe( () => { //这个函数会返回一个销毁函数,此销毁函数会自动在组件销毁的时候调用
setNumber(store.getState().counter2.number)
})
},[]) //useEffect的第二个参数是依赖变量的数组,当这个依赖数组发生变化的时候才会执行函数
return (
<div>
<p>{number}</p>
<button >+</button>
<button >-</button>
</div>
)
}
通过 useContext 拿到上下文传过来得值。
用connect改写上面的Counter1和Counter2:
import React, {useState,useEffect,useContext} from 'react'
// import store from '../store'
// import actions from '../store/actions_type'
import { bindActionCreators } from 'redux'
import actions from '../store/actions/Counter1'
import ReactReduxContext from '../react-redux/context'
import {connect} from '../react-redux'
//函数组件
function Couter (props) {
return (
<div>
<p>{props.number}</p>
<button onClick={props.add}>+</button>
<button onClick={props.minus}>-</button>
</div>
)
}
let mapStateToProps = state => state.counter1 //从store中拿到当前组件得属性
let mapDispatchToProps = actions //把当前组件得动作进行派发
export default connect(
mapStateToProps,
actions
)(Couter)
Counter2也是如此。
* mapStateToProps 把当前组件的状态映射为当前组件的属性对象 {counter1:{number: 0},counter2: {number:0}} * mapDispatchToProps connect 内部会把actions进行绑定,然后把绑定结果对象作为当前组件的属性对象,直接在绑定事件的时候用props.add或者props.minus
分别看一下Provider和connect的源码实现:
Provider.js
import React from 'react'
import ReactReduxContext from './context'
/**
* Provider 有个store属性,需要向下传递这个属性
* @param {*} props
*/
export default function (props) {
return (
<ReactReduxContext.Provider value={{store:props.store}}>
{props.children}
</ReactReduxContext.Provider>
)
}
connect.js
import React, {useContext, useState, useEffect} from 'react'
import ReactReduxContext from './context'
import { bindActionCreators } from 'redux'
export default function (mapStateToProps,mapDispatchToProps) {
return function(OldComponent){
//返回一个组件
return function(props) {
//获取state
let context = useContext(ReactReduxContext) //context.store
let [state,setState] = useState(mapStateToProps(context.store.getState()))
//利用useState只会在初始化的时候绑定一次
let [boundActions] = useState( () => bindActionCreators(mapDispatchToProps,context.store.dispatch))
//订阅事件
useEffect(() => {
return context.store.subscribe(() => {
setState(mapStateToProps(context.store.getState()))
})
},[])
//派发事件 这种方式派发事件的时候每次render都会进行一次事件的绑定,耗费性能
// let boundActions = bindActionCreators(mapDispatchToProps,context.store.dispatch)
//返回组件
return <OldComponent {...state} {...boundActions} />
}
}
}
* connect 是个高阶组件
* 1.获取从上下文传过来得值 store
* 2.将store.getState()=>mapStateToProps 成为OldComponent得属性对象
* 3.负责订阅store状态变化事件,当仓库状态发生变化后要刷新当前组件以及OldComponent
* 4.把actions进行绑定,然后把绑定后得结果boundActions作为属性对象传递给OldComponent
connenct并不会改变它“连接”的组件,而是提供一个经过包裹的connect组件。 conenct接受4个参数,分别是mapStateToProps,DispatchToProps,mergeProps,options(使用时注意参数位置顺序)。
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
1.mapStateToProps(state, ownProps) 方法允许我们将store中的数据作为props绑定到组件中,只要store更新了就会调用mapStateToProps方法,mapStateToProps返回的结果必须是object对象,该对象中的值将会更新到组件中
const mapStateToProps = (state) => {
return ({
count: state.counter.count
})
}
2.mapDispatchToProps(dispatch, [ownProps]) 第二个参数允许我们将action作为props绑定到组件中,mapDispatchToProps希望你返回包含对应action的object对象
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increase: (...args) => dispatch(actions.increase(...args)),
decrease: (...args) => dispatch(actions.decrease(...args))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(yourComponent)
3.mergeProps(stateProps, dispatchProps, ownProps) 该参数非必须,redux默认会帮你把更新维护一个新的props对象,类似调用Object.assign({}, ownProps, stateProps, dispatchProps)。
4.[options] (Object)
如果指定这个参数,可以定制 connector 的行为。
4.5 redux 中间件middlewares
正常我们的redux是这样的工作流程,action -> reducer ,这相当于是同步操作,由dispathch触发action之后直接去reducer执行相应的操作。但有时候我们会实现一些异步任务,像点击按钮 -> 获取服务器数据 ->渲染视图,这个时候就需要引入中间件改变redux同步执行流程,形成异步流程来实现我们的任务。有了中间件redux的工作流程就是action -> 中间件 -> reducer ,点击按钮就相当于dispatch 触发action,接着就是服务器获取数据middlewares执行,成功获取数据后触发reducer对应的操作,更新需要渲染的视图数据。
中间件的机制就是改变数据流,实现异步acation,日志输出,异常报告等功能。
4.5.1 日志中间件
希望在store状态变更之前打印日志
//1.备份原生的dispatch方法
let dispatch = store.dispatch
//2.重写dispatch方法 做一些额外操作
store.dispatch = function (action) {
console.log('老状态',store.getState())
//触发原生dispatch方法
dispatch(action)
console.log('新状态', store.getState())
}
在重写dispatch方法之前先备份原生的dispatch方法,这个写法和vue中监听数组的变化方式很相似。
这种写法是直接对dispatch进行重写,不利于维护,当有多个中间件的时候也没法调用,所以要改成下面的写法
import {createStore} from 'redux'
import reducer from './reducers/Counter'
const store = createStore(reducer)
//1.备份原生的dispatch方法
// let dispatch = store.dispatch
// //2.重写dispatch方法 做一些额外操作
// store.dispatch = function (action) {
// console.log('老状态',store.getState())
// //触发原生dispatch方法
// dispatch(action)
// console.log('新状态', store.getState())
// }
function logger ( {dispatch, getState}) { //dispatch是重写后的dispatch
return function (next) { //next代表原生的dispatch方法,调用下一个中间件或者store.dispatch 级联
//改写后的dispatch方法
return function (action) {
console.log('老状态', getState())
next(action) //store.dispatch(action)
console.log('新状态', getState())
dispatch(action) //此时的disptch是重写后的dispach方法,这样会造成死循环
}
}
}
function applyMiddleware(middleware) { //middleware = logger
return function(createStore) {
return function (reducer) {
let store = createStore(reducer) // 返回的是原始的未修改后的store
let dispatch
middleware = middleware({ //logger执行 需要传参getState 和 dispatch 此时的 middleware = function(next)
getState: store.getState,
dispatch: action => dispatch(action) //指向改写后的新的dispatch 不能是store.dispatch
})
dispatch = middleware(store.dispatch) //执行上面返回的middleware ,store.dispatch 代表next
return {
...store,
dispatch
}
}
}
}
let store = applyMiddleware(logger)(createStore)(reducer)
export default store
4.5.2 thunk中间件
function thunk ({dispatch, getState}) {
return function (next) {
return function (action) {
if(typeof action === 'function') {
action(dispatch, getState)
}else {
next(action)
}
}
}
}
function applyMiddleware(middleware) { //middleware = logger
return function(createStore) {
return function (reducer) {
let store = createStore(reducer) // 返回的是原始的未修改锅的store
let dispatch
middleware = middleware({ //logger执行 需要传参getState 和 dispatch 此时的 middleware = function(next)
getState: store.getState,
dispatch: action => dispatch(action) //指向改写后的新的dispatch 不能是store.dispatch
})
dispatch = middleware(store.dispatch) //执行上面返回的middleware ,store.dispatch 代表next
return {
...store,
dispatch
}
}
}
}
let store = applyMiddleware(thunk)(createStore)(reducer)
export default store
actions.js
import * as TYPES from './actions_type'
export default {
add() {
return {type: TYPES.ADD}
},
minus() {
return {type: TYPES.MINUS}
},
//正常的actions必须是一个纯对象,不能是函数{type:'add'}
thunkAdd() {
return function (dispatch, getState) {
setTimeout(function() {
dispatch({type: TYPES.ADD})
},1000)
}
}
}
<button onClick={boundActions.thunkAdd}>thunkAdd</button>
点击按钮触发thunkAdd这个actions,它是一个函数actions,所以在调用之前进行判断,thunk这个中间件就是用来给store派发函数类型的actions的
4.5.3 Promise 中间件
Action Creator 返回一个 Promise 对象。
function promise ({dispatch, getState}) {
return function (next) {
return function (action) {
if(typeof action.then === 'function') {
action.then(dispatch)
//action.then( result => dispatch(dispatch))
}else {
next(action)
}
}
}
}
function applyMiddleware(middleware) { //middleware = logger
return function(createStore) {
return function (reducer) {
let store = createStore(reducer) // 返回的是原始的未修改锅的store
let dispatch
middleware = middleware({ //logger执行 需要传参getState 和 dispatch 此时的 middleware = function(next)
getState: store.getState,
dispatch: action => dispatch(action) //指向改写后的新的dispatch 不能是store.dispatch
})
dispatch = middleware(store.dispatch) //执行上面返回的middleware ,store.dispatch 代表next
return {
...store,
dispatch
}
}
}
}
let store = applyMiddleware(promise)(createStore)(reducer)
export default store
actions.js
import * as TYPES from './actions_type'
export default {
add() {
return {type: TYPES.ADD}
},
minus() {
return {type: TYPES.MINUS}
},
//正常的actions必须是一个纯对象,不能是函数{type:'add'}
thunkAdd() {
return function (dispatch, getState) {
setTimeout(function() {
dispatch({type: TYPES.ADD})
},1000)
}
},
promiseAdd(){
return new Promise(function (resolve) {
setTimeout(function () {
resolve({type: TYPES.ADD})
},1000)
})
}
}
<button onClick={boundActions.promiseAdd}>promiseAdd</button>
Action 本身是一个 Promise,它 resolve 以后的值应该是一个 Action 对象,会被dispatch
方法送出(action.then(dispatch)
)
4.5.4 级联中间件
上面我们调用的中间件都是单个调用,传进applyMiddleware的参数也是单个的,但是我们要想一次调用多个中间件,那么传到applymiddware的参数就是个数组,这个时候就需要级联处理,让他们一次执行。
applyMiddleware.js
function applyMiddleware(...middlewares ) { //middleware = logger
return function(createStore) {
return function (reducer) {
let store = createStore(reducer) // 返回的是原始的未修改锅的store
let middlewareAPI = {
getState: store.getState,
dispatch: action => dispatch(action) //指向改写后的新的dispatch 不能是store.dispatch
}
let dispatch
chain= middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
// dispatch = middleware(store.dispatch) //执行上面返回的middleware ,store.dispatch 代表next
return {
...store,
dispatch
}
}
}
}
let store = applyMiddleware(promise,thunk, logger)(createStore)(reducer)
compose.js
function compose(...fns) {
return fns.reduce((a,b) => function(...args) {
return a(b(...args))
})
}
上面代码中,所有中间件被放进了一个数组chain
,然后嵌套执行,最后执行store.dispatch
。可以看到,中间件内部(middlewareAPI
)可以拿到getState
和dispatch
这两个方法。
compose方法就是合并改造后的dispatch方法,。最终的结果就是 retrurn promise(thunk(logger(dispatch)))
相关优秀文章推荐:
阮一峰:http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html
深入理解redux中间件:https://www.jianshu.com/p/ae7b5a2f78ae
redux如何实现组件之间数据共享:https://segmentfault.com/a/1190000009403046
https://www.cnblogs.com/wy1935/p/7109701.html
Redux解决了什么问题:https://www.ucloud.cn/yun/104048.html
https://www.cnblogs.com/rudylemon/p/redux.html
https://zhuanlan.zhihu.com/p/50247513
https://www.redux.org.cn/docs/recipes/reducers/UsingCombineReducers.html