Redux 排序与分析 [I: 减速器与调度]。
本文适合已经会使用
redux
的同学阅读。好吧,其实就是自己梳理写给自己看的,顺便分享一下~
最近在知乎上看到了一篇整理讨论Vuex、Flux、Redux、Redux-saga、Dva、MobX
这些状态管理库的设计思想的文章,链接在此。
想到以前最开始学习React
的时候,被React
全家桶折磨的那么痛苦,虽然当时也看过别人分享过Redux
的分享,也稍微看了一些源码,目前已经忘记了差不多了。虽然网上已经有太多的分享和整理,但是主要是想写给自己看,温故而知新~
Redux 的基本用法
抛开 react
,Redux
非常简单,以官方github
上的示例为样板,稍微改变点东西,以方便我在本地进行调试,我直接把静态资源的redux.js
下载到本地,然后进行代码调试。下面是最简单的使用示例:
const type = {
increment: 'INCREMENT',
decrement: 'DECREMENT',
}
const initialState = {
count: 0
}
const reducer = function (state = initialState, action) {
switch(action.type) {
case type.increment:
return {
...state,
count: state.count + 1
}
case type.decrement:
return {
...state,
count: state.count - 1
}
default:
return state
}
}
const store = Redux.createStore(reducer)
store.subscribe(() => console.log(store.getState()))
setTimeout(() => {
store.dispatch({ type: type.increment })
// 1
store.dispatch({ type: type.increment })
// 2
store.dispatch({ type: type.decrement })
// 1
}, 4000)
这个代码就是创建了一个reducer
然后,使用createStore
创建了一个store
,然后监听了变化,只要变化就打印当前的state
,最后在setTimeout
中进行dispatch
让state
发生变化。
可以看到这里暂时还没有使用applyMiddleware
函数,也就是没有使用中间件,这个放在后面说。
入口函数
先看看源码结构:
Redux
这个变量上, 挂载了一些属性,可以从示例代码上看到,入口函数就是createStore
了。首先看一下index.js
入口文件。
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
...
function isCrushed() {}
...
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
基本没有什么东西,引入了其他文件的导出,然后汇集一起导出了。
createStore
export default function createStore(reducer, preloadedState, enhancer) {
...
}
可以看到createStore
函数,接受三个参数,第一个就是传入的reducer
处理函数,第二个是预置的state
,第三个用来对createStore
函数进行增强的中间件等工具方法了。一般enhancer
我们都是传入的是applyMiddleware
函数的返回值。
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.'
)
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
函数一进来就会判断第二参数和第三个参数的正确行,这里可以看到,如果第二参数传入的是函数,并且第三个参数没传,这里会把它当成enhancer
。这就是我们常这样使用的方式:createStore(reducer, applyMiddleware(...))
,只传入两个参数。再之后就是返回enhancer(createStore)(reducer, preloadedState)
的结果,这个放在后面说。
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer // 当前的 reducer
let currentState = preloadedState // 当前的state,默认为 preloaderState,一般为 undefined
let currentListeners = [] // 监听变化的函数队列
let nextListeners = currentListeners // 变化之后,下一次的监听变化的函数队列
let isDispatching = false // 是否处于 Dispatching 状态
紧接着就是判断了reducer
是否为函数,然后声明了几个变量,用来初始化。
中间代码会声明一些操作函数,包括我们常用的dispatch
,这些函数基本上就是对上面声明的这些变量在进行操作。我们直接看最后面。
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[?observable]: observable
}
首先在返回之前,调用了dispatch
函数,对state
进行了初始化,传入了一个对象,包含对type
的声明,也就是一个action
。
dispatch 函数
整个 createStore
直接执行的代码没几行,重要的动作在dispatch({ type: ActionTypes.INIT })
这句上,看看dispatch
的实现。
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
- 首先判断传入的
action
必须要是一个字面量对象。 - 再判断
action
是否存在type
属性。 - 判断是否有其他的
dispatch
正在执行。 - 将
isDispatching
设置为true
,然后将当前的state
和传入的action
传入到reducer
去执行,然后得到结果赋值给currentState
。 - 执行完第4步,就代表
state
已经修改完了,所以将isDispatching
置为false
。 - 将
currentListeners
赋值为nextListeners
,然后进行遍历,执行里面所有的监听回调函数,最后返回action
。
逻辑比较简单,当我们调用dispatch({ type: ActionTypes.INIT })
的时候,最终是将currentState
和{ type: ActionTypes.INIT }
传入到了我们实现的reducer
函数里面。
const reducer = function (state = initialState, action) {
switch(action.type) {
case type.increment:
return {
...state,
count: state.count + 1
}
case type.decrement:
return {
...state,
count: state.count - 1
}
default:
return state
}
}
这时候,currentState
为undefined
,type
为ActionTypes.INIT
,根据代码,可以看到最终返回的是initialState
。这就是初始的state
的赋值场景了。
在我们的应用代码里面会去调用dispatch
函数,其实每次都是直接调用了reducer
然后去遍历执行了listeners
队列。简单吧,很简单。根据源码反映,只要dispatch
函数的执行,listeners
就会被执行,并不是state
变了才回去执行,由此可以看出redux
的监听就是全量的调用,粒度好像有点大哦。
subscribe 函数
对状态进行监听(其实是对执行dispatch监听),是调用的subscribe
函数,它还会返回一个值用来取消当前函数的监听。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
...
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
- 判断 listener 是否为函数。
- 将当前的
isSubscribed
标志,置为true
。 - 调用
ensureCanMutateNextListeners
函数对currentListeners
进行浅拷贝,赋值给nextListeners
。 - 将传入的
listener
入nextListeners
队列。 - 返回
unsubscribe
函数,这个函数左右就是在nextListeners
队列中找到listener
然后进行删除。
getState 函数
直接将currentState
变量的值进行了返回。
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
replaceReducer 函数
传入一个新的reducer
函数,进行替换,最后调用一下dispatch
函数,得到reducer
对应的state
。
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
// This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({ type: ActionTypes.REPLACE })
}
observable 函数
这个函数,我没有使用过,暂时没有碰到相关的场景,根据源码,我理解为,类似于subscribe
,但是这个监听回调可以结合符合约定的第三方observable
库。比如源码注释提到的https://github.com/tc39/proposal-observable
。
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[?observable]() {
return this
}
}
}
小结
以上梳理了没有使用中间件增强的基本流程,让我最深刻的就只有listener
的调用,只是和dispatch
调用有关,至于state
有没有修改只是简单赋值而已。
redux
应用了很多函数式的编程技巧,就比如subscribe
函数最后直接返回取消订阅的函数。
原文地址
未完待续~
上一篇: 前车之鉴--iOS 事件响应链
下一篇: 前车之鉴、春季交易的失败情景和新发现
推荐阅读
-
Redux 排序与分析 [I: 减速器与调度]。
-
正负偏差变量 即 d2+、d2- 分别表示决策值中超出和未达到目标值的部分。而 di+、di- 均大于 0 刚性约束和目标约束(柔性目标约束有偏差) 在多目标规划中,>=/<= 在刚性约束中保持不变。当需要将约束条件转换为柔性约束条件时,需要将 >=/<= 更改为 =(因为已经有 d2+、d2- 用来表示正负偏差),并附加上 (+dii-di+) 注意这里是 +di、-di+!之所以是 +di,-di+,是因为需要将目标还原为最接近的原始刚性约束条件 优先级因素和权重因素 对多个目标进行优先排序和优先排序 目标规划的目标函数 是所有偏差变量的加权和。值得注意的是,这个加权和都取最小值。而 di+ 和 dii- 并不一定要出现在每个不同的需求层次中。具体分析需要具体问题具体分析 下面是一个例子: 题目中说设备 B 既要求充分利用,又要求尽可能不加班,那么列出的时间计量表达式即为:min z = P3 (d3- + d3 +) 使用 + 而不是 -d3 + 的原因是:正负偏差不可能同时存在,必须有 di+di=0 (因为判定值不可能同时大于目标值和小于目标值),而前面是 min,所以只要取 + 并让 di+ 和 dii- 都为正值即可。因此,得出以下规则: 最后,给出示例和相应的解法: 问题:某企业生产 A 和 B 两种产品,需要使用 A、B、C 三种设备。下表显示了与工时和设备使用限制有关的产品利润率。问该企业应如何组织生产以实现下列目标? (1) 力争利润目标不低于 1 500 美元; (2) 考虑到市场需求,A、B 两种产品的生产比例应尽量保持在 1:2; (3)设备 A 是贵重设备,严禁超时使用; (4)设备 C 可以适当加班,但要控制;设备 B 要求充分利用,但尽量不加班。 从重要性来看,设备 B 的重要性是设备 C 的三倍。 建立相应的目标规划模型并求解。 解:设企业生产 A、B 两种产品的件数分别为 x1、x2,并建立相应的目标计划模型: 以下为顺序求解法,利用 LINGO 求解: 1 级目标: 模型。 设置。 variable/1..2/:x;! s_con_num/1...4/:g,dplus,dminus;!所需软约束数量(g=dplus=dminus 数量)及相关参数; s_con(s_con_num);! s_con(s_con_num,variable):c;!软约束系数; 结束集 数据。 g=1500 0 16 15. c=200 300 2 -1 4 0 0 5; 结束数据 min=dminus(1);!第一个目标函数;!对应于 min=z 的第一小部分;! 2*x(1)+2*x(2)<12;!硬约束 @for(s_con_num(i):@sum(variable(j):c(i,j)*x(j))+dminus(i)-dplus(i)=g(i)); !使用设置完成的数据构建软约束表达式; ! !软约束表达式 @for(variable:@gin(x)); !将变量约束为整数; ! 结束 此时,第一级目标的最优值为 0,第一级偏差为 0: 第二级目标: !求 dminus(1)=0,然后求解第二级目标。 模型。 设置。 变量/1..2/:x;!设置:变量/1..2/:x; ! s_con_num/1...4/:g,dplus,dminus;!软约束数量及相关参数; s_con(s_con_num(s_con_num));! s_con(s_con_num,variable):c;! 软约束系数; s_con(s_con_num,variable):c;! 结束集 数据。 g=1500 0 16 15; c=200 300 2 -1 4 0 0 5; 结束数据 min=dminus(2)+dplus(2);!第二个目标函数 2*x(1)+2*x(2)<12;!硬约束 @for(s_con_num(i):@sum(variable(j):c(i,j)*x(j))+dminus(i)-dplus(i)=g(i)); ! 软约束表达式;! dminus(1)=0; !第一个目标结果 @for(variable:@gin(x)); ! 结束 此时,第二个目标的最优值为 0,偏差为 0: 第三目标 !求 dminus(2)=0,然后求解第三个目标。 模型。 设置。 变量/1..2/:x;!设置:变量/1..2/:x; ! s_con_num/1...4/:g,dplus,dminus;!软约束数量及相关参数; s_con(s_con_num(s_con_num));! s_con(s_con_num,variable):c;! 软约束系数; s_con(s_con_num,variable):c;! 结束集 数据。 g=1500 0 16 15; c=200 300 2 -1 4 0 0 5; 结束数据 min=3*dminus(3)+3*dplus(3)+dminus(4);!第三个目标函数。 2*x(1)+2*x(2)<12;!硬约束 @for(s_con_num(i):@sum(variable(j):c(i,j)*x(j))+dminus(i)-dplus(i)=g(i)); ! 软约束表达式;! dminus(1)=0; !第一个目标约束条件; ! dminus(2)+dplus(2)=0; !第二个目标约束条件 @for(variable:@gin(x));! 结束 最终结果为 x1=2,x2=4,dplus(1)=100,最优利润为