1、源码结构
当前redux master分支已全部使用typescript,为便于理解可使用4.x分支,仍是js版本)
2、核心方法 createStore.js
- 创建一个redux
store
掌管状态树 - 改变
store
中数据的唯一方式就是调用dispatch()
- 一个app中应该仅具有一个
store
,多个模块可以使用combineReducers
合并不同的reducer
params & returns:
@param {Function} reducer
传入当前状态树和action,返回新的状态树
//reducer实例
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id === action.id)
? {...todo, completed: !todo.completed}
: todo
)
default:
return state
}
}
@param{any} [preloadedState]
初始状态(可以用来进行状态回溯)
@param{Function} [enhancer]
通过第三方中间件增强能力,如redux-thunk增强异步dispatch的能力
@return {Store}
返回一个store
定义的5个变量
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
ensureCanMutateNextListeners()
防止在dispach过程中改变监听列表,所以用slice()
做了一个复制
getState()
通过getState()
获取currentState
subscribe(listener)
@param {function} listener
@return {function} unsunscribe()
添加一个listener监听函数,每次状态改变都会调用。
返回一个函数来取消监听。
通过nextListeners.splice(index)
取消监听
ensureCanMutateNextListeners()
nextListeners.push(listener)
//复制新数组,防止dispatch时的回调会改变当前listener数组
dispatch(action)
dispatch一个action。这是触发状态更改的唯一方法。
基本实现仅支持普通对象(异步promise需要中间件)
param {Object} action
return {Object}
实现过程
- 调用util中的
isPlainObject()
判断action是否是"plain obj" - 判断
action
是否有type
- 判断isDispatching,reducer中不能进行dispatch
- 核心代码
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()
}
- 每一次dispatch 会走一边全部的reducer
(Reducer很多时可能存在性能问题) - 遍历调用listener()执行回调,过程中可能会改变linstener数组,不过在订阅阶段已经复制数组避免了这一问题。
replaceReducer()
将currentReducer替换为nextReducer
observable()
最小的可观察订阅方法。
@param {Object} 观察者 任何可以用作观察者的对象。
观察者对象应该有一个next
方法。
@returns {subscription} 一个带有unsubscribe
方法的对象,它可以
用于从 store 中取消订阅 observable,
3、combineReducers.js
@param {Object} reducers
@returns {Function}
核心思路
- 通过key遍历参数reducers,替换finalReducer的key
- 通过finalReducer的key,同样是for循环遍历执行每一个reducer。也就是说:对于多个 reducer ,一次 dispatch 会依次把所有 reducer 执行一次。
Q:如此设计,每次dispatch都要执行全部的reducer会不会有性能问题?
A:官方的回复:不会,但可以使用第三方库优化
However, even if you happen to have many different reducer functions
composed together, and even with deeply nested state, reducer speed is
unlikely to be a problem. JavaScript engines are capable of running a
very large number of function calls per second, and most of your
reducers are probably just using a switch statement and returning the
existing state by default in response to most actions.If you actually are concerned about reducer performance, you can use a
utility such asredux-ignore
orreduxr-scoped-reducer
to ensure that
only certain reducers listen to specific actions. You can also use
redux-log-slow-reducers
to do some performance benchmarking.
值得一提的是,代码中频繁出现的
process.env.NODE_ENV !== 'production'
node中,有全局变量process表示的是当前的node进程。
process.env包含着关于系统环境的信息,NODE_ENV是一个用户自定义的变量,用于判断生产环境或开发环境。
4、compose.js
@param {…Function} funcs 要组合的函数。
@returns {Function} 通过组合参数函数获得的函数 * 从右到左。例如,compose(f, g, h) 等同于做 * (…args) => f(g(h(…args)))。
有多个 enhancer 时,使用 compose 组装后传给 createStore。
核心代码
export default function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
此功能类似 _.flow([funcs])
5、applyMiddleware.js
@param {…Function} middlewares 要应用的中间件链。
@returns {Function} 应用中间件的store enhancer。
请注意,每个中间件都将被赋予 dispatch
和 getState
函数作为命名参数
核心代码
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),
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch,
}
}
}