注:redux核心代码位于src目录下,因此以目录结构来分析,文中仅粘贴部分核心代码。
src
index.js
import createStore from './createStore'
// 省略导入
......
// 定义一个空函数
function isCrushed() {}
// 代码压缩后,会对变量进行重命名,也就是通过这种方式,减少字节,判断函数名是否发生变化
// 来确定是否使用了压缩后的版本。
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
// 判断函数的运行环境,在非生产环境下运行时,isCrushed函数名不是'isCrushed',则说明
// 自视运行的是压缩后代码,对开发者提出警告⚠️开发环境下使用压缩文件,会增加额外的构建时间。
warning(
'You are currently using minified code outside of NODE_ENV === "production"......'
)
}
// 输出redux的核心api
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
复制代码
createStore
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
/**
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
*
* @param {Function} [enhancer] The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
// createStore构建一个全局唯一的store用于存储全局状态。store是一个树?结构对象,可读并在一定情况下
// 可写,当然你也可以直接对sotre对象进行改写,那么产生的出乎意料的bug也会让你惊喜,redux本身并没有对
// store的写操作进行限制,全凭使用者的自觉。ps:为了不出bug,不要直接改写store数据。
export default function createStore(reducer, preloadedState, enhancer) {
// createStore接收最多三个参数
// @reducer<:Function> 一个纯函数衍生器,用于从旧的状态,衍生新的状态,并且这个新状态是全新的拷贝。
// preloadedState<:Object> 初始化状态,用于从一个基础状态构建一个初始store。
// enhance<:Function> reducer的增强器,可以调用比如中间件函数等,来增强redux功能。
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// enhancer可能是第二个参数,此时需要纠正各参数值。曾考虑为什么不将enhancer和preloadedState参数
// 调换位置,如此便不需要这一步处理,后想到大多数情况下我们可能不会使用enhancer,所以在参数设计时,尽量
// 将次要参数的位置向后,或通过对象{key:value}的形式传入。
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
// enhancer必须为一个function
throw new Error('Expected the enhancer to be a function.')
}
// 如果传入了enhancer,则使用enhaner增强createReducer函数,最后生成store
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
// 类型校验
throw new Error('Expected the reducer to be a function.')
}
// 预定义一系列可用变量
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
// 注册事件存储在栈中,当前后两次栈相同时,进行一次拷贝,因为通常操作的nextListeners,这是
// 最新的事件栈。防止在操作nextListeners时进行了写操作,导致修改了currentListeners。
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* @returns {any} The current state tree of your application.
*/
function getState() {
// getState方法很简单,直接返回当前的store,即返回currentState。获取store时,可能
// 正处于某个action在dispatch的状态,那么此时的state,我们必然可以从组件内获取,不需要
// 通过api来获取。
// 假设:如果在dispatch时获取state,这种行为往往和我们预期不太相同,因为我们通常想要获取的
// 是最新状态的state,而此时的store状态即将被更新。
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing...... '
)
}
return currentState
}
/**
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
// redux支持订阅store状态的变化,可向subscribe传入一个注册函数。
if (typeof listener !== 'function') {
// 类型校验
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
// 禁止在dispatch过程中进行订阅。
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
let isSubscribed = true
// 确保可以安全操作nextListeners
ensureCanMutateNextListeners()
// 将订阅函数推入订阅栈
nextListeners.push(listener)
// 订阅完成后,返回一个取消订阅的方法
return function unsubscribe() {
if (!isSubscribed) {
// 防止反复取消订阅,后面使用了splice方法,保证不会取订错误的函数
return
}
if (isDispatching) {
// dispatch时禁止取消订阅
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 将订阅函数从订阅栈中清除
nextListeners.splice(index, 1)
}
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing “what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
// dispatch是唯一可以改变store的渠道,action要求只能是简单对象,并包含一个type属性
function dispatch(action) {
if (!isPlainObject(action)) {
// action需要是一个简单函数,可以是对象字面量 a = {} 或 Object.create(null)
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
// 要求action必须包含type属性
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
// 不可以同时进行多个dispatch,避免同时修改store树?,导致状态紊乱
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 进入dispatching状态,此时会禁止一些特殊操作,如上面的subscribe, unsubscribe, getState
// 传入reducer当前currentState和action生成新的store
currentState = currentReducer(currentState, action)
} finally {
// store更新完毕后,修改dispatching状态
isDispatching = false
}
// dispatch后执行订阅的回调函数,到了兑现约定的时候了,需要通知订阅函数,store发生变化了。
// 并把listerners进行更新
// 思考:其实可以在listerner复制这里进行拷贝,便不需要ensureCanMutateNextListeners函数了
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 问题:为什么不把新的store传给回调函数???通过getstate吗?
// 答案:想了一下,传递store给监听函数,有可能我们并不需要使用store,那么传递后还需要一个额外
// 的指针内存。可以在需要时,通过getState获取最新的store。
listener()
}
// 返回action
return action
}
/**
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
// 作用如名字,替换reducer。如store一版,reducer也只需要一个。但在代码中,为了优化,可能
// 会做一些代码分割的操作。可能会根据模块分割store,分割reducer。但最终还是需要将reducer
// 组装在一起的。
if (typeof nextReducer !== 'function') {
// 参数类型检验,nextReducer必须是函数
throw new Error('Expected the nextReducer to be a function.')
}
// 替换reducer,触发一个全局action, 通知reducer发生了替换。
// 问题:为什么此处不做isDispatching的状态限制?
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
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
}
}
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
复制代码
compose
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
export default function compose(...funcs) {
// compose函数,可将多个函数参数组成合并成一个函数,参数从右自左执行。
if (funcs.length === 0) {
return arg => arg
}
// 思考: 需要加一步对参数类型的校验,否则会引发错误。
// if (funcs.some((func) => !func instanceof Function)) {
// throw new Error('所有参数需要是函数类型')
// }
if (funcs.length === 1) {
return funcs[0]
}
// reduce遍历整个参数序列,时间复杂度On(n)。合并成的对象参数将取自最后一个函数的参数。
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
复制代码
combineReducer
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
// 此方法用于检查store和reduces的key是否完全匹配,不匹配则发出提示
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
// 获取传入reducers的序列key。
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
if (reducerKeys.length === 0) {
// combineReducers接收的reducers参数必须是一个对象,内容为key:value序列。
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
if (!isPlainObject(inputState)) {
// inputState需为简单对象
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
// state和reducers必须key匹配,unexpectedKeys为inputState中不与reducers匹配的key
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
// 将不匹配的key传送给unexpectedKeyCache参数
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
// 如果在发生replaceReducer,则退出这段程序
if (action && action.type === ActionTypes.REPLACE) return
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
// 此方法用于检验reducer是否做了默认处理,在任何情况下都需要衍生一个新的state或返回currentState或者null
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
// 测试reducer功能边界条件处理是否健全
if (typeof initialState === 'undefined') {
// 保证传入的reducers中总有一个处理通用type的case,即我们常写的reducer函数switch语句中
// 对于default情况的处理,通常返回上一个状态的store。可以返回null但是不能返回undefined。
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
// 通过一个随机的验错type❌,来检查reducer是否对默认情况进行了处理。对于任何未知的type,
// reducer都需要有一个合格的返回值。并且警告⚠️不能够对redux中内置的几个type做处理。
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${
ActionTypes.INIT
} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
/**
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
// 合并reducers的核心方法,返回一个全新的reducer。
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
// 遍历reducers
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
// 在非生产环境下,reducers的每一个key都需要有一个子reducer作为值,不能为undefined
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
// 仅在reducers的各个属性值为函数时进行复制
finalReducers[key] = reducers[key]
}
}
// finalReducers的获取也可以这么写
// const finalReducers = reducers.reduce((res, reducer, key) => {
// if (reducer instanceof Function) {
// res[key] = reducer;
// } else {
// if (process.env.NODE_ENV !== 'production') {
// warning(`No reducer provided for key "${key}"`)
// }
// }
// return res;
// }, {})
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
// 检验reducer是否合规
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 最终返回的合并后的reducer
return function combination(state = {}, action) {
if (shapeAssertionError) {
// 如果reducer不合规,抛出错误。?
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
// 非生产环境下,检查state和reduces的key是否完全匹配
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
// 如果检验不合规,进行提示
warning(warningMessage)
}
}
// 前后状态是否发生变化
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
// 使用reducer来衍生新状态
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
// 如果某个action中没对所有的边界条件进行处理,抛出错误。
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
// 判断前后状态是否发生了变化,只要有部分状态发生变化,则store发生了变化
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// store发生变化,返回新的状态,否则返回旧状态
return hasChanged ? nextState : state
}
}
复制代码
bindActionCreators
function bindActionCreator(actionCreator, dispatch) {
// 核心方法,为action生成器绑定dispatch,不需要每次从store.dispatch获取,当然这种方式也可以。
return function() {
// 为actioncreater绑定运行时时上下文环境,确保其执行时this指向其所属环境
return dispatch(actionCreator.apply(this, arguments))
}
}
/**
* @param {Function|Object} actionCreators An object whose values are action
* creator functions. One handy way to obtain it is to use ES6 `import * as`
* syntax. You may also pass a single function.
*
* @param {Function} dispatch The `dispatch` function available on your Redux
* store.
*
* @returns {Function|Object} The object mimicking the original object, but with
* every action creator wrapped into the `dispatch` call. If you passed a
* function as `actionCreators`, the return value will also be a single
* function.
*/
// 输出方法
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
// 如果直接传入了一个函数,则直接进行绑定
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
// actionCreators类型校验,必须为对象类型或函数。
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"?`
)
}
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
// 检验actionCreators中每个actionCreator是否为函数
// 仅为actionCreator是函数的部分绑定dispatch
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
// 返回绑定dispatch后的actionCreators
return boundActionCreators
}
复制代码
applyMiddleware
import compose from './compose'
/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
*
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 遍历调用middware增强dispatch方法,中间件可以链式合并。
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
复制代码
utils
actionTypes
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
const randomString = () =>
Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')
// redux内置的action type, 用于在store初始化,replaceReducer,注入代码时通知或校验
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`,
REPLACE: `@@redux/REPLACE${randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
export default ActionTypes
复制代码
isPlainObject
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
// 最终proto为Object
proto = Object.getPrototypeOf(proto)
}
// 判断obj是不是对象字面量或Object构建的实例对象
return Object.getPrototypeOf(obj) === proto
}
复制代码
warning
/**
* Prints a warning in the console if it exists.
*
* @param {String} message The warning message.
* @returns {void}
*/
export default function warning(message) {
/* eslint-disable no-console */
// 判断console方法是否可调用
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
/* eslint-enable no-console */
// 抛出警告信息⚠️,如果开启异常断点,则会在自处停止
try {
// This error was thrown as a convenience so that if you enable
// "break on all exceptions" in your console,
// it would pause the execution at this line.
throw new Error(message)
} catch (e) {} // eslint-disable-line no-empty
}
复制代码
Redux阅读体验
学习到
-
Object.keys(someobj).map((v, k) => someobj[k])
可以替代for...of...
支持es6可以使用Objec.entries -
compose方法的reduce实现,reduce真的很强!
(...args) => (..params) => args.reduce((res, curr) => res(curr(..params)))
// reduce便捷在于返回的无限制和累积效应
// 无论是map, foreach, for...in...都无法达到这么优秀的品质
复制代码
-
let lock = true;/* somewhere */ lock = false;
想不到合适名字,就叫运行锁,开发中这种模式很常用,一个bool类型的变量,便可以区别两种状态,就像寄存器的两个状态。
-
接口设计
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
复制代码
只暴露尽可能少且必要的接口,接口名可以体现功效。
复制代码
- 代码死,人要灵活,观察到语言的规则和现象,都可以被利用。
function isCrushed() {}
在压缩情况下isCrushed.name !== 'isCrushed'
复制代码
优点
- redux自身体积很小,小几百行代码,对于一个大型项目来说,体积上简直是冰山一角,所以不必担心包大小。
- flux架构的践行者,结合 react,mvc工作流,管理你的应用状态。
- 单向流,以前总觉的单向流用起来很不爽快,状态链任性组织,随着工程越来越大,维护也越来越难,难到自己写的代码都看不清晰。所以要让你的应用易于组织,易于维护,那就让你的数据流尽量简单。就像,世间凡事大河,定然不会有勾缠旋绕,方能源远流长,万流汇聚。
- 源码读起来很流畅,了解flux架构后,完全可以自己阅读源码进行学习。
不足
- 细粒度控制不够好,对于状态的更新,监听(subscribe)都是全量的,可以比较mobx。
- 不支持异步操作,需要配合thunk,saga等中间件。
- 单向流意味着灵活度的降低,当然这是易于组织维护的代价,并且需要额外增加许多模版代码。
- reducers的合并也仅仅支持到一维,超复杂场景或许需要一个树?结构的reducer。