从源代码的入口文件发现,其实 redux 最终就只是导出了一个对象,对象中有几个方法,代码以下:html
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
复制代码
因此重点分析几个方法:react
方法中定义的一些变量:git
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
复制代码
这些变量会被 dispatch
或者别的方法引用,从而造成闭包。这些变量不会被释放。github
建立 srore
的方法最终返回的是一个对象。对象中含有比较重要的方法dispatch,subscribe,getState
。编程
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
复制代码
其中 createStore
的第三个参数是应用中间件来作一些加强操做的。redux
if (typeof enhancer !== 'undefined') { // 若是加强方法存在就对 createStore 进行加强
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
复制代码
其中 subscribe
用来注册监听方法,每一次注册后会将监听方法维护到数组currentListeners
中,currentListeners
是 createStore
中的一个变量,因为被 subscribe
引用着因此造成了一个闭包。也就是经过闭包来维护状态。数组
let currentListeners = []
复制代码
dispatch
方法用来分发 action
, 函数里面会生成新的 currentState
, 会执行全部注册了的函数。闭包
核心代码:app
try {
isDispatching = true
currentState = currentReducer(currentState, action) // 生成新的 state
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
} // 遍历执行注册函数
复制代码
仅仅用来得到当前的 state
:异步
function getState() {
return currentState
}
复制代码
函数中定义的一些变量,
const finalReducers = {}
const finalReducerKeys = Object.keys(finalReducers)
复制代码
这个函数最后返回的是一个函数 combination
, 返回的函数中引用了 finalReducers
和 finalReducerKeys
,造成了闭包。
出于业务场景考虑,不一样的模块采用不一样的 reducer
进行处理,因此 reducer
函数有不少。这些 reducer
会遍历执行。
每一次 dispatch
一个 action
的时候就会执行
currentState = currentReducer(currentState, action) // 生成新的 state
复制代码
这里的 currentReducer
就是返回的 combination
函数。combination
函数中的核心代码:
function combination(state = {}, action) {
...
let hasChanged = false
// 每一次 reducer 执行的时候都会生成一个新的对象来做为新的 state
const nextState = {}
// 经过 for 循环遍历 reducer
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 获取当前的 state
const previousStateForKey = state[key]
// 执行相应的 reducer 后会生成新的 state
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 给新的 state 赋值
nextState[key] = nextStateForKey
// 若是是一个简单类型好比 string,number
// 若是先后值同样就不会触发改变
// 但若是 state 中某个值是一个对象,
// 尽管先后对象中的值同样,可是引用地址变化,仍是会触发改变
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 因此若是简单值没有变化而且没有对象的引用地址改变就会返回原来的 state
return hasChanged ? nextState : state
}
复制代码
结合 react-redux
中向 redux
订阅的方法发现
subscribe() {
const { store } = this.props // 这里的 store 是 createStore 方法执行后返回的对象
this.unsubscribe = store.subscribe(() => { // 经过订阅方法注册监听事件
const newStoreState = store.getState() // 获取新的 state
if (!this._isMounted) {
return
}
// 经过使用函数替代对象传入 setState 的方式可以获得组件的 state 和 props 属性可靠的值。
this.setState(providerState => {
// 若是值是同样的就不会触发更新
if (providerState.storeState === newStoreState) {
return null
}
return { storeState: newStoreState }
})
})
// Actions might have been dispatched between render and mount - handle those
const postMountStoreState = store.getState()
if (postMountStoreState !== this.state.storeState) {
this.setState({ storeState: postMountStoreState })
}
}
复制代码
在注册的 listen
方法中会发现若是最 新的state和原来的state同样
就不会触发 setState
方法的执行,从而就不会触发 render
。
源码:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => { // 接收 createStore 函数做为参数
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)
} // 中间件函数接收的 API 参数,可以获取到当前的 state 和 createStore 函数的参数
// 因此这里就向中间件函数中传递了参数
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 经过函数组合生成一个新的 dispatch 函数
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
} // 这里返回的是最后生成的 store,相比不使用中间件的区别是对 dispatch 进行了加强。
}
}
复制代码
结合 createStore
中的源码:
return enhancer(createStore)(reducer, preloadedState)
复制代码
因此上面 applyMiddleware
中返回的函数就是这里的 enhancer
方法,接收 createStore
做为参数。
(reducer, preloadedState)
对应着中间件中的 (...args)
。
react-redux
经过提供 Provider
组件将 store
和整个应用中的组件联系起来。确保整个组件均可以得到 store
, 这是经过 Context
来实现的。
Provider
组件最终渲染的组件:
render() {
const Context = this.props.context || ReactReduxContext
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
复制代码
其中 state
的定义以下:
const { store } = props
this.state = {
storeState: store.getState(),
store
}
复制代码
因此 Provider
给应用提供 store
的写法以下,属性名必须是 store
。
<Provider store={store}>
<Router />
</Provider>
复制代码
redux-thunk
是一个中间件,直接看中间件的源代码是绝对不可能看明白的
。
中间件不是一个完整的个体。它是为了丰富或者扩展某个模块而出现的,其中会调用一些原来的模块的方法,因此若是不看源模块的对应的方法实现,根本没法理解。
因此要想看懂一个中间件,必须结合源模块的代码一块儿看。
JavaScript 语言是传值调用,它的 Thunk 函数含义有所不一样。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数做为参数。
如何让 dispatch 分发一个函数,也就是 action creator??
dispatch
的参数只能是一个普通的对象,若是要让参数是一个函数,须要使用中间件 redux-thunk
。
设计思想就是一种面向切面编程AOP,对函数行为的加强,也是装饰模式的使用。
redux-thunk
源码:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
复制代码
若是只是用了 thunk
,那么最终加强版的 dispatch
就是
action => {
// 当 dispatch 参数是一个函数的时候执行这里
if (typeof action === 'function') {
// 这里的 dispatch 就是最原始的 dispatch
// 因此 action 函数中能够直接使用参数 dispatch 和 getState 函数
return action(dispatch, getState, extraArgument);
}
return next(action); // 这里的 next 是 store.dispatch
}
复制代码
异步操做若是使用 action creator
, 则至少要送出两个 Action:
action creator
函数中送出第二个 Action
代码实例:
handleClick = () => {
const { dispatch } = this.props
dispatch(this.action); // 发出第一个 action(函数)
}
action = (dispatch, getState) => setTimeout(() => {
dispatch({ type: 'REQUESTSTART' })
}, 1000) // 发出第二个 action(普通对象)
复制代码
异步代码的处理必定要使用 redux-thunk
吗?
非也。在触发含有异步代码的函数执行时,把 dispatch
函数做为一个参数传给函数,而后这个异步函数里面在合适的时机调用 dispatch
发出 action
就行。
上面的异步代码可改写以下:
handleClick = () => {
const { dispatch } = this.props
this.action(dispatch);
}
action = dispatch => setTimeout(() => {
dispatch({ type: 'REQUESTSTART' })
}, 1000)
复制代码
不过相比 redux-thunk
有个缺陷就是不能获取 getState
这个方法。