最新內容,請在github閱讀。同時,All issue and star welcomed!
1.深入分析redux中的createStore方法
我們首先以一個官方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()
2.深入分析redux中的combineReducers方法
首先貼上源碼(關鍵部分):
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
}
3.深入分析redux中的bindActionCreators方法
我們直接給出源代碼:
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是對我們的函數進行了一次包裹
}
4.深入分析redux中的applyMiddleware方法
直接貼上源碼:
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等函數
5.redux的數據流
參考資料: