我们首先以一个官方demo来说,代码如下:
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import Counter from './components/Counter'
import counter from './reducers'
const store = createStore(counter)
const rootEl = document.getElementById('root')
const render = () => ReactDOM.render(
<Counter
value={store.getState()}
onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
\/>,
rootEl
)
render()
store.subscribe(render)
下面我们来看看createStore的作用:
const store = createStore(counter)
//下面就是counter函数
export default (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
createStore表示我们创建一个数据库,这个数据库计算下一个状态的方式是通过counter来完成的。而且,如果你要计算下一个状态,需要给我传入当前状态,以及你需要的特定的操作的名称,即Action。同时,这个数据库也提供了很多其他的方法。
getState: 获取当前的数据库的状态 subscribe: 当你对数据库发出一个指令,而且数据库根据这个指令已经计算得到新的状态 以后需要执行的回调函数 dispatch:发出一个Action,告诉数据库你要干嘛。数据库会根据当前的状态以及你的命令类型计算得到新的状态。计算完成以后,我们要执行subscribe添加的所有的回调函数. replaceReducer:用一个新的store替换掉我们当前的store用来计算我们的新的state。如果你的app支持code splitting, 而且你想要动态加载reducer。同时如果你要为你的Redux支持Hot Reloading也需要 observable:
其中observable的内容如下:
function observable() {
var outerSubscribe = subscribe
return {
subscribe(observer) {
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
var unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
该方法的实现不是本文讨论的重点,你可以查看文末的参考资料。此时我们回到上面的源代码。
第一步:createStore构建数据库并传入计算新state的规则
const store = createStore(counter)
数据库会有上面的getState,subscribe,dispatch,replaceReducer,observable等各种方法。你可以给它注册事件,也可以从它哪里获取到数据。
第二步:注册回调函数。当数据库收到指令,并且通过它计算到新的值的时候重新render。也就是说,每次dispatch导致数据库状态变化后我们都会重新render
store.subscribe(render)
第三步:渲染render页面
const render = () => ReactDOM.render(
<Counter
value={store.getState()}
//获取数据库状态
onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
//向数据库发送指令
onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
\/>,
rootEl
)
render()
首先贴上源码(关键部分):
export default function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers)
var finalReducers = {}
//对每一个reducer进行循环,将那些导出是函数的reducer保存到finalReducers中
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
var finalReducerKeys = Object.keys(finalReducers)
//得到我们关注的reducer所有的键名
if (NODE_ENV !== 'production') {
var unexpectedKeyCache = {}
}
var sanityError
try {
assertReducerSanity(finalReducers)
} catch (e) {
sanityError = e
}
//此时我们的combineReducers返回一个函数了,也就是最终结合起来的reducer
//和普通的reducer一样,接收state与action为两个参数
return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}
if (NODE_ENV !== 'production') {
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
var hasChanged = false
var nextState = {}
//http://redux.js.org/docs/api/combineReducers.html
//此时key为["todos","counter"]
//store.dispatch({
// type: 'ADD_TODO',
// text: 'Use Redux'
// })
// 此时表示我们会要reducers去处理,但是具体让那个reducer处理是要我们自己选择的~~
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
//此时如todos,counter两个key
var reducer = finalReducers[key]
//得到某个key对应的具体的reducer函数。如上面这种情况key和value是一致的,为todos和counter
var previousStateForKey = state[key]
//对于每一个reducer只会处理相应的key,所以我们的state肯定是如下的格式
//{todos:[],counter:0},也就是我们 ${ActionTypes.INIT}的初始值
var nextStateForKey = reducer(previousStateForKey, action)
//第一次循环如果是todos,那么我们会获取state中的todos的值以及action传入计算得到新的todos值
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
//更新这个key的值
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
//这个state是否发生变化
}
//如果没有一个属性的值发生变化,那么hasChanged就是为false
return hasChanged ? nextState : state
}
}
我们真实使用的使用是如下方式的:
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})
你应该知道,我们这里导出的调用combineReducers后的结果其实就是我们组合后的reducer函数,这个函数会被传入到createStore中,就像以前我们传入单个reducer函数到createSStore中一样。对于每一个reducer函数的编写也是有具体的要求的:
function assertReducerSanity(reducers) {
Object.keys(reducers).forEach(key => {
var reducer = reducers[key]
var initialState = reducer(undefined, { type: ActionTypes.INIT })
//通过对我们的reducer执行ActionTypes.INIT,得到这个reducer的初始值
if (typeof initialState === '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.`
)
}
var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
//我们的每一个reducer传入的第一个参数是state,第二个参数是Action
//(1)不要处理@@redux这个空间下的Action或者@@redux下的${ActionTypes.INIT},他们是私有的
//(2)对于我们未知的action我们必须返回当前的state状态,除非这个Action是undefined.
// 此时因为action是undefined,所以我们返回初始值,而不管Action本身的类型
if (typeof reducer(undefined, { type }) === 'undefined') {
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.`
)
}
})
}
注意三点:通过对我们的reducer执行ActionTypes.INIT,得到这个reducer的初始值;不要处理@@redux这个空间下的Action或者@@redux下的${ActionTypes.INIT},他们是私有的;对于我们未知的action我们必须返回当前的state状态,除非这个Action是undefined, 此时因为action是undefined,所以我们返回初始值(不是当前值),而不管Action本身的类型。还有一点需要注意:
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
//此时如todos,counter两个key
var reducer = finalReducers[key]
//得到某个key对应的具体的reducer函数。如上面这种情况key和value是一致的,为todos和counter
var previousStateForKey = state[key]
//对于每一个reducer只会处理相应的key,所以我们的state肯定是如下的格式
//{todos:[],counter:0},也就是我们 ${ActionTypes.INIT}的初始值
var nextStateForKey = reducer(previousStateForKey, action)
//第一次循环如果是todos,那么我们会获取state中的todos的值以及action传入计算得到新的todos值
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
//更新这个key的值
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
//这个state是否发生变化
}
对于这个for循环来说,传入一个Action的情况下,我们对将两个reducer函数(todos与counter)都执行它。对于不处理这个Action.type的reducer,我们会直接根据reducer返回当前的值,即currentState。同时,我们也必须注意,我们返回的state肯定是如下的类型,即肯定包含todos与counter属性,即使是获取初始${ActionTypes.INIT}值( nextState[key] = nextStateForKey):
{
todos:[],
counter:0
}
我们直接给出源代码:
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
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"?`
)
}
var keys = Object.keys(actionCreators)
//获取keys
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
//得到我们的actionCreator方法
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
//boundActionCreators["removeTodo"]=function(){}
//boundActionCreators["addTodo"]=function(){}
//所以,返回的内容很简单,就是把我们自己的函数包装一下,返回一个新的函数
//当你调用这个新的函数的时候,新函数会直接dispatch我们的旧函数调用的值
//同时将新函数的参数也传递给旧函数
}
return boundActionCreators
}
代码很简单,我们先给出调用的例子:
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
//通过bindActionCreators包裹的函数必须是action creators
export function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
}
}
let boundActionCreators = bindActionCreators(TodoActionCreators, dispatch)
所以,返回的内容很简单,就是把我们自己的函数包装一下,返回一个新的函数 当你调用这个新的函数的时候,新函数会直接dispatch我们的旧函数调用后的值 同时将新函数的参数也传递给旧函数。所以上面的例子得到的数据类型如下:
{
addTodo:function(){},
removeTodo:function(){}
//这里的function是对我们的函数进行了一次包裹
}
直接贴上源码:
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
//如果中间件的个数为1,那么获取第一个中间件
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
//last表示最后一个中间件,而rest标签除了最后一个中间件的其他中间件
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
//初始值为h(...args),reduceRight第一个函数的第一个参数为previousValue
}
//For example, compose(f, g, h) is identical to doing
//(...args) => f(g(h(...args))).
下面是applyMiddleware源码:
export default function applyMiddleware(...middlewares) {
//createStore=>reducer, preloadedState, enhancer
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
//得到一个store
var dispatch = store.dispatch
//获取到store上的dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
// (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
//[middleware1,middleware2,middleware3]
// middleware1(middleware2(middleware3(last(store.dispatch))))
return {
...store,
dispatch
}
}
}
下面是一个middleware的中间件的例子:
function logger({ getState }) {
return (next) => (action) => {
console.log('will dispatch', action)
// Call the next dispatch method in the middleware chain.
let returnValue = next(action)
console.log('state after dispatch', getState())
// This will likely be the action itself, unless
// a middleware further in chain changed it.
return returnValue
}
}
从源码中我们可以看到,中间件的执行是分为下面三个步骤的:
第一步:调用我们middleware,传入getState与dispatch
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
第二次:再次调用我们的middleware,传入store.dispatch
dispatch = compose(...chain)(store.dispatch)
第三次:不管什么时候调用,调用方式如何,只要传入action即可。这也是我们唯一需要自己控制的一步~
return {
...store,
dispatch
}
还有一点需要注意,我们真实调用applyMiddleware时候的方式是:
let store = createStore(
todos,
[ 'Use Redux' ],
applyMiddleware(logger)
)
很显然,我们上面的applyMiddleware第二级函数参数是createStore,那么是什么时候调用的呢?我们看看createStore方法(只是摘取关键部分演示):
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
//applyMiddleware(logger)此处继续传入createStore与reducer,preloadedState
//同时return直接返回了。但是我们要知道,在applyMiddleWare中我们创建了自己的
//store,即 var store = createStore(reducer, preloadedState, enhancer)
return enhancer(createStore)(reducer, preloadedState)
}
}
是不是明白了,在createStore的时候我们传入了applyMiddleware(logger)。那么后面我们在createStore里面继续传入了createStore与reducer, preloadedState等函数
同时我特别建议读一下这篇文章,我觉得他将redux的内容讲的特别通熟易懂,特别是mapStateToProps,mapDispatchToProps,actionCreator的作用,以及当mapDispatchToProps是对象的情况下,我们是如何将action发送出去的,以及此时actionCreator创建的action到底是什么,这些问题你将得到满意的答案。最后,我盗用他的图来说明一下redux的具体过程:
参考资料:
[[译]深入浅出Redux中间件](http://www.tuicool.com/articles/u6JRjyz)