redux的原理和中间件
redux是一个状态管理的容器,它主要是用来保证程序行为的一致性,通过单一数据源来修改状态,方便我们使用工具调试
基本使用
redux主要有如下几个步骤
- 创建一个store来存放数据
createStore - store里面通过
reducer来初始化state,并定义state的修改规则 - 在业务层通过
dispatch提交action来修改数据 - action提交到
reducer函数中,根据传入的action的type, 返回新的state
创建store (src/store/index.js) 主要是用createStore
import { createStore } from 'redux'
const reducer = function (state = 0, { type, payload = 1 }) {
switch (type) {
case 'ADD': {
return state + payload
}
case 'MINUS':
return state - payload
default:
return state
}
}
const store = createStore(reducer)
export default store
引入store, 修改App.js
redux需要手动的更新,类组件有一个this.forceUpdate(), 函数组件需要手动的实现一个forceUpdate
import store from './store'
import { useEffect, useReducer } from 'react'
function App() {
let state = store.getState()
let [, forceUpdate] = useReducer(x => x+ 1, 0)
useEffect(() => {
let unListen = store.subscribe(() => {
forceUpdate()
})
return () => {
unListen && unListen()
}
}, [])
return (
<div className="App">
Hello world
<h5>{state}</h5>
<button onClick={() => store.dispatch({ type: 'MINUS' })}>减一</button>
<button onClick={() => store.dispatch({ type: 'ADD' })}>加一</button>
</div>
);
}
export default App;
主要就是一下几点
createStore创建storereducer初始化,根据type和payload修改函数getState获取状态值statedispatch提交action通过reducer的规则进行更新subscribe订阅,当state更新后通知, 记得组件卸载后取消订阅
实现Redux
其实redux本身没有做很多事情,只是单纯的接受reducer管理状态,所以实现redux本身很简单
export function createStore(reducer) {
let state
let listeners = []
function getState() {
return state
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => {
listener && listener()
})
return action
}
function subscribe(listener) {
listeners.push(listener)
return () => {
let index = listeners.findIndex(item => item === listener)
listeners.splice(index, 1)
}
}
// 触发初始化
dispatch("init")
return {
getState,
dispatch,
subscribe,
}
}
redux中间件
由于redux的功能单一,并且它自己对于dispatch的action只能接受纯对象,如果我们想要异步的获取数据,然后更新state状态的话,就需要一些
中间件来实现。
redux.js:200 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
我们可以通过redux-thunk来对传入的action是函数进行处理
- 安装
redux-thunk的依赖
npm i redux-thunk
- 通过
redux提供的拓展中间件的方法进行拓展
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))
- 通过中间件后,我们可以使用一些函数的action, 函数的回调参数有3个,第一个是加强后的
dispatch, 第二个为getState, 第三个为自定义参数
dispatch((dispatch) => {
setTimeout(() => {
dispatch({type: 'ADD'})
}, 3000)
}, 1000)
实现中间件
实现中间件之前,我们需要实现一些redux的辅助函数applyMiddleware 和 createStore
实现applyMiddleware
首先要确定applyMiddleware接受的参数
createStore接受了applyMiddleware的返回值,增强了dispatch的功能,但是其他原来的功能需要保留
+ if (enhancer) {
+ return enhancer(createStore)(reducer)
+ }
- 从上面的
createStore中可以看出applyMiddleware需要根据createStore和reducer返回原来的功能,然后再原来的功能上增加dispatch的部分
// applyMiddleware
export function applyMiddleware(...middlewares) {
return (createStore) => (reducer) => {
// 保持原来的功能
let store = createStore(reducer)
return {
...store
}
}
}
- 在保持基本功能的情况下,我们需要通过中间件对于dispatch进行加强,来覆盖原来的dispatch的功能
thunk一个函数,接受2个参数getState,dispatch,所以我们要创建这2个参数, 然后执行中间件的函数,通过闭包的方式保留getState和dispatch
export function applyMiddleware(...middlewares) {
return (createStore) => (reducer) => {
// 保持原来的功能
let store = createStore(reducer)
+ let dispatch = store.dispatch
+ const midApi = {
+ getState: store.getState,
+ dispatch: (action, ...args) => {
+ return dispatch(action, ...args)
+ }
+ }
+ const middlewareChain = middlewares.map(middleware => middleware(midApi))
return {
...store
}
}
}
-
现在我们要思考的是需要一个函数执行所有的中间件
middlewareChain(并不知道applyMiddleware会接受几个中间件),然后返回一个加强版的dispatch来覆盖原来的store.dispatch, 我们假设有一个compose函数接收所有的中间件和原来的dispatch,然后返回一个加强后的dispatch- 假设又一个
compose函数
export function applyMiddleware(...middlewares) { return (createStore) => (reducer) => { // 保持原来的功能 let store = createStore(reducer) let dispatch = store.dispatch const midApi = { getState: store.getState, dispatch: (action, ...args) => { return dispatch(action, ...args) } } const middlewareChain = middlewares.map(middleware => middleware(midApi)) + dispatch = compose(middlewareChain)(dispatch) return { ...store } } }- 实现compose函数, 实现middlewareChain的数组聚合,只有一个方法
reduce来实现数组的聚合,然后需要返回一个函数,接受原来的dispatch
// 先假设只有一个中间件或者没有中间件 function compose(middlewareChain) { // 如果没有的话直接返回原来的dispatch if (middlewareChain.length === 0) { return arg => arg } if (middlewareChain.length === 1) { //只有一个函数的时候,返回函数,执行的时候会接受原来的dispatch return middlewareChain[0] } // 多个中间件 reduce 返回一个函数,执行一次就执行所有的函数 return middlewareChain.reduce((a, b) => (...args) => a(b(...args))) } - 假设又一个
总结
这里我们就实现了applyMiddleware和createStore,整体的代码
export function applyMiddleware(...middlewares) {
return (createStore) => (reducer) => {
// 保持原来的功能
let store = createStore(reducer)
let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: (action, ...args) => {
return dispatch(action, ...args)
}
}
const middlewareChain = middlewares.map(middleware => middleware(midApi))
console.log(middlewareChain[0])
dispatch = compose(middlewareChain)(dispatch)
console.log(dispatch)
return {
...store,
dispatch
}
}
}
// enhancer 是applyMiddleware函数执行的返回值, reducer肯定要传入applyMiddleware中,
export function createStore(reducer, enhancer) {
let state
let listeners = []
if (enhancer) {
return enhancer(createStore)(reducer)
}
function getState() {
return state
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => {
listener && listener()
})
return action
}
function subscribe(listener) {
listeners.push(listener)
return () => {
let index = listeners.findIndex(item => item === listener)
listeners.splice(index, 1)
}
}
// 触发初始化
dispatch('init')
return {
getState,
dispatch,
subscribe,
}
}
function compose(middlewareChain) {
// 如果没有的话直接返回原来的dispatch
if (middlewareChain.length === 0) {
return arg => arg
}
if (middlewareChain.length === 1) {
//只有一个函数的时候,返回函数,执行的时候会接受原来的dispatch
return middlewareChain[0]
}
// reduce 返回一个函数,执行一次就执行所有的函数
return middlewareChain.reduce((a, b) => (...args) => a(b(...args)))
}
实现中间件
首先我们要明白每一个中间件,需要保存上一个下一个中间件的执行过程(next),又需要保存接受action。
-
redux-thunk和redux-logger的实现- 基于上面的实现,我们会首先执行中间件,然后传递2个参数给它
dispatch和getState, - 然后需要保存执行下一个中间件的过程
next和 返回一个可以接受action的dispatch函数
export function thunk({getState, dispatch}) { return (next) => { return (action) => { if(typeof action === 'function') { console.log('-thunk 执行-') return action(dispatch, getState) } return next(action) } } } export function logger({getState, dispatch}) { return (next) => { return action => { console.log('-pre state-', getState()) // 触发动作再获取state console.log('next', next) const returnValue = next(action) console.log(returnValue) let nextState = getState() console.log('-next state-', nextState) return returnValue } } } - 基于上面的实现,我们会首先执行中间件,然后传递2个参数给它
-
2个中间件的整体执行流程
const store = createStore(reducer, applyMiddleware(thunk, logger)) // App.js dispatch((dispatch, getState) => { setTimeout(() => { dispatch({ type: 'ADD' }) }, 1000) })- 运用过中间键后,每一次的dispatch都会经过
thunk和logger进行处理 App.js中dispatch执行,会进入到加强过的thunk的回调函数中接受action执行
(action) => { if(typeof action === 'function') { console.log('-thunk 执行-') return action(dispatch, getState) } return next(action) }- 知道是函数的话,然后会执行这个函数,传入
dispatch和getState参数
setTimeout(() => { dispatch({ type: 'ADD' }) }, 1000)- 这里又会执行到
dispatch函数,所以又会进入到第二部的加强版的dispatch中,这次action不是一个函数,就执行到下一个中间件return next(action) - 由于闭包的关系,next中间件就是
logger函数的部分, 然后执行完成
action => { console.log('-pre state-', getState()) // 触发动作再获取state console.log('next', next) const returnValue = next(action) console.log(returnValue) let nextState = getState() console.log('-next state-', nextState) return returnValue } - 运用过中间键后,每一次的dispatch都会经过