用redux写过一些小项目,感觉有段时间不用快要忘记。所以写下我对redux的理解(和react-redux结合使用)。作为笔记。redux版本是3.6.0
。react-redux版本是5.0.4
。
首先打开github的redux项目,在源码中,主要有以下文件:
这也是redux的组成部分。utils
文件夹只有一个warning.js
,用来打印错误信息。index.js
用来导出相关内容。关键的是其他五个文件。
applyMiddleware.js
,bindActionCreator.js
,combineReducers.js
,compose.js
,createStore.js
。
一个redux的流程是这样的。
1、在View上,用户会做出一个动作(比如点击)。
2、Store通过其方法dispatch这个动作,这个动作往往是一个对象({type: 'click', payload: {}}
),或者是一个函数(需要redux-thunk
或其他中间件处理)。对于Action
,一般都是大写并且声明为常量。
store.dispatch({
type: 'SELECT_BOOKNAME',
payload: {
text: 'react'
}
})
3、Reducers会收到这个动作,并进行相应的处理。
import { SELECT_BOOKNAME } from '../actions';
const selectedBookName = (state = 'java', action) => {
switch (action.type) {
case SELECT_BOOKNAME:
return action.payload.text;
default:
return state;
}
}
export default selectedBookName;
reduce处理会改变应用的state(redux应用中,只有一个store)。如上action.payload.text
。默认情况下state的值为java
,现在变成了react
(action中传过来的参数)。但是这个state不是应用整个的state,正如这个reducer只是一个子reducer而已。所以这里作为参数的state实际上是应用的state.selectedBookName
属性。
4、state改变了,一般而言,页面(View)会重新render。依赖于state的数据也会改变。
下面是源码分析
compose
本来把compose放在后面,想想还是提前了。
compose是一个辅助函数,效果很简单,它只是简化了深层嵌套调用函数的问题。compose(f, g)
的行为等价于(...args) => f(g(...args))
。简单的说,compose(f, g)
是一个函数,接受的参数会传给函数g
,函数g
执行,将函数返回的结果作为函数f
的参数。然后执行函数f
。compose(f, g)
的返回值即函数f
执行后的返回值。
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)))
}
这里主要使用的是Array.prototype.reduce函数。reduce(function(acc, currentValue, currentIndex, arr), initialValue)
。其中initialValue
是可选的。如果提供了initialValue
,那么acc
的值是initialValue
,currentValue
的值是数组的第一个元素。如果没提供initialValue
,那么acc
的值是数组的第一个值,currentValue
的值是数组的第二个值。
Array.prototype.reduce
函数中的参数函数(callback)将会从左到右处理数组的每一个值。然后将最终的结果返回。在这里,callback
返回一个函数。
(...args) => a(b(...args));
所以compose
函数的返回值也是一个函数(A),其接受一个或多个参数,然后将参数传入到compose
函数中最后一个参数执行。执行的结果作为compose
函数中倒数第二个函数的参数。依次执行。
compose(fn1, fn2, fn3)(1,2,3);
// 等价于
fn1(fn2(fn3(1,2,3)))
上述代码将参数1,2,3
作为fn3的参数。fn3(1,2,3)
执行的结果作为fn2
的参数…
createStore
去掉了注释和辅助函数以及store的一些函数的具体的实现(dispatch、getState、subscribe、replaceReducer)。将精力全部集中到createStore这个函数。
// createStore.js
export default function createStore(reducer, preloadedState, enhancer) {
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) // 注1
}
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() {}
function subscribe(listener) {}
function dispatch(action) {}
function replaceReducer(nextReducer) {}
function observable() {}
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
在创建store的时候,我们可以传入初始状态(preloadedState
),但这不是必须的。所以一开始会判断。
enhancer一般是applyMiddleware
或者是compose
函数。通过它可以使用一些第三方组件,比如redux-logger
,redux-thunk
等。这个参数也是可选的。
一般我们创建的store是这样的。
const store = createStore(
reducers,
preloadState,
compose(
applyMiddleware(...middleware),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
)
// 或者是
const store = createStore(
reducers,
preloadState,
applyMiddleware(...middleware)
)
不管如何,如果有enhancer,那么就会转而执行enhancer(注1)。假设这里是applyMiddleware
。执行applyMiddleware
并传入参数。并把函数本身传入作为参数。然后执行enhancer(createStore)
(返回的肯定是一个函数,从下文applyMiddleware
可以看到)并传入(reducer, preloadedState)
。
return enhancer(createStore)(reducer, preloadedState) // 注1
applyMiddleware
// applyMiddleware.js
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => { // 注2
const store = createStore(reducer, preloadedState, enhancer) // 注3
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
注2这里不是很容易理解的,需要对照上文的createStore
。
return enhancer(createStore)(reducer, preloadedState) // 注1
return (createStore) => (reducer, preloadedState, enhancer) => { // 注2
相互对照,方便理解。那么流程就是在createStore
的时候,如果有enhancer(如applyMiddleware
),就先执行enhancer,并传入createStore
函数本身以及它的参数reducer, preloadState
。
再查看注3,也就是applyMiddleware
的开始部分。因为applyMiddleware
的参数是一个个组件,对于这些组件,执行createStore
函数,并传入第一次createStore传下来的两个参数。所以流程就是createStore->applyMiddleware->createStore
。由于这次的enhancer是undefined
,所以在createStore.js
中会继续往下执行,返回一个store
。绕了个弯。
const store = createStore(reducer, preloadedState, enhancer)
然后从注3往下执行。关键来了。先插述点东西。
————————————————————————-
one:
以中间件的最简单方式为例,创建一个中间件通常是这样的(如logger中间件):
const logger = store => next => action => { // next相当于dispatch
console.log('dispatching', action);
let result = next(action); // 相当于dispatch(action),返回的结果仍然是action
console.log('next state', store.getState()); // 注4
return result; // 返回action,供其他中间件处理
}
————————————————————————-
插述完毕。
之前说到从注3往下执行。接下来分析这一块代码:
// applyMiddleware.js
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
middlewares
是所有中间件组成的数组。调用map
方法,传入中间件(是一个函数,比如上述的logger中间件),并执行这个函数,传入middlewareAPI
变量(相当于store)。
const logger = store => next => action => { // next相当于dispatch,往下文看
console.log('dispatching', action);
let result = next(action); // 相当于dispatch(action),返回的结果仍然是action
console.log('next state', store.getState()); // 注4
return result; // 返回action,供其他中间件处理
}
再看看吧,logger是middleware
,middlewareAPI
相当于store,所以可以在注4调用store.getState()
方法。
middlewares.map
返回值是一个数组,赋值给chain
,数组的每一项是函数。即中间件(logger)执行后返回的函数(fn = next => action => {}
)。
然后增强dispatch。
dispatch = compose(...chain)(store.dispatch)
刚说的chain
中的每一项是个数组,并且compose
的作用就是从右到左执行它的函数参数。所以需要执行fn = next => action => {}
这个函数,这个函数的返回值还是一个函数(爽不爽!!!,函数是这样的:fn = action => {}
,这个函数能接受action
作为参数,当然是dispatch
了)。对于fn = next => action => {}
传入store.dispatch
,所以next
就是store.dispatch
(上文也提到)。然后将返回的函数next
再传递给倒数第二个参数(从右往左),然后一直往左,直到第一个。想象一下:
chain = [returnedLogger1, returnedLogger2, returnedLogger3];
首先store.dispatch
传递给returnedLogger3
,返回值是一个函数,fn = action => {}
,能接受action
作为参数,其实际上还是store.dispatch
。所以就是store.dispatch
一直作为参数从右向左传递,直到compose
最左边的函数参数执行完毕,返回的store.dispatch
赋值给最初的dispatch
。666666版本的dispatch出现了!!!
然后就是applyMiddleware.js
内层函数的返回值了。
return {
...store,
dispatch
}
...store
表示解构store
这个对象(其中有dispatch,subscribe,getState等),其中dispatch
会被增强版本的dispatch
覆盖。
至此,applyMiddleware.js
分析完毕。
下面是一个测试:
const logger1 = store => next => action => {
console.log(111);
let result = next(action);
console.log(222);
return result;
}
const logger2 = store => next => action => {
console.log(111111);
let result = next(action);
console.log(222222);
return result;
}
const logger3 = store => next => action => {
console.log(111111111);
let result = next(action);
console.log(222222222);
return result;
}
const configureStore = (preloadState={aaa: 'aaaa'}) => {
const store = createStore(
reducers,
preloadState,
compose(
applyMiddleware(thunk, logger1, logger2, logger3),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
)
return store;
}
以applyMiddleware(thunk, logger1, logger2, logger3),
这种形式,打印出的结果是:
111, 111111, 111111111, 222222222, 222222, 222
combineReducers
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') { // 诚心找事吧!
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') { // 小的reducer肯定是function啊
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 忽略中间这些异常处理吧,比如preloadState中的key在reducers(参数reducer,是一个对象)中不存在。
return function combination(state = {}, action) { // 注5
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]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
一般而言,finalReducers
和reducers
是相同的(见上述代码)。从异常处理下面开始分析。finalReducerKeys
是一个数组。
// 例子reducers
export default combineReducers({
postByBookName,
selectedBookName,
user
})
比如上面这个reducers,finalReducerKeys
的值是:
["postByBookName","selectedBookName","user"]
由于在createStore
的时候传入了大的reducer
,所以store.dispatch
一个action
的时候大的reducer
会处理这个action
。
createStore.js
中有两行代码:
let currentReducer = reducer
let currentState = preloadedState
// 下面这行代码在store.dispatch函数中
currentState = currentReducer(currentState, action)
反映在上述代码就是执行注5的函数。
return function combination(state = {}, action) { // 注5
关键是这5行代码,以上述例子reducers
其中的selectedBookName
为例。
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
key
是字符串selectedBookName
。
reducer
是selectedBookName
对应的reducer。我的是:
const selectedBookName = (state = 'java', action) => {
switch (action.type) {
case SELECT_BOOKNAME:
return action.payload.text;
default:
return state;
}
}
export default selectedBookName;
previousStateForKey
是undefined
。
nextStateForKey
是selectedBookName
函数执行的结果,如果匹配上了返回action.payload.text
,假如是”react”。
nextState["selectedBookName"] = "react"
。
此时由于previousStateForKey
不等于nextStateForKey
,所以hasChanged
的值为true。返回nextState
。
至此,combineReducers.js
分析完毕。
bindActionCreators
这是个辅助函数。比如:
const test = () => {
type: 'test',
}
以前我们的用法是dispatch(test())
,使用了这个辅助函数,我们只需要test()
。就是少写了dispatch
这么回事。一般情况下我不会用到。
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
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') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
} else {
warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)
}
}
return boundActionCreators
}
上述源码分为两种情况,一种是typeof actionCreator === 'function'
,就是我举例的那种。一种是actionCreator
是一个对象。通常在这种情况出现:
import * as Actions from '../actions'
这样Actions
是一个对象,每个import
出的函数就是Actions
的一个属性值。然后向一种一样的处理。通常我不会使用这个辅助函数,当我使用react-redux
的时候,我可能会用到。
import * as CounterActions from '../actions'
const mapStateToProps = (state) => ({
counter: state.counter
})
function mapDispatchToProps(dispatch) {
return bindActionCreators(CounterActions, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
然后我直接调用CounterActions.myAction()
就直接dispatch
了。
结语
花了一晚上时间,梳理了redux的结构,加深了理解。对源码进行了细致的分析。如果有更多的想要和我交流,可以加我:
QQ: 1227620310
微信: a127620310