// 章節 0 - introduction.jshtml
// 你可能已經看過這張著名的 flux 的單向數據流圖了。node
// 在這個教程裏,咱們會一步步地向你介紹上圖裏的各個概念。
// 咱們會把這些概念分紅單獨的章節來介紹它們存在的意義和做用。
// 在最後,當咱們理解了每個概念後,咱們會發現這張圖真是意義深遠啊!react
// 在咱們開始以前,咱們先聊下一 flux 存在的意義以及咱們爲何須要它。
// 假設咱們正在構建一個網站應用,那麼這個網站應用會由什麼組成呢?
// 1) 模板/HTML = View
// 2) 填充視圖的數據 = Model
// 3) 獲取數據、將全部視圖組裝在一塊兒、響應用戶事件、
// 數據操做等等的邏輯 = Controllerwebpack
// 這是咱們熟知的很是典型的 MVC,但它和 flux 的概念實際上是很像的,
// 只是在某些表述上有些小小的不一樣:
// - Model 看起來像 Store
// - 用戶事件、數據操做以及它們的處理程序看起來像
// "action creators" -> action -> dispatcher -> callback
// - View 看起來像 React view (或者其它相似的概念)git
// 因此,flux 就只是一個新名詞麼?不全是,可是新名詞是很重要的,
// 由於經過引入這些新術語咱們能夠更準確地表述各類專業術語。
// 舉一個例子,獲取數據是一個 action,一個點擊是一個 action,
// 一個 input 變化也是一個 action 等等。咱們都已經習慣了從咱們的應用裏分發 action,
// 只是以不一樣的方式稱呼它們。 不一樣於直接修改 Model 和 View,
// Flux 確保全部 action 首先經過一個 dispatcher,
// 而後再是 store,最後通知全部的 store 監聽器。github
// 爲了弄清楚 MVC 和 flux 的不一樣,
// 咱們舉一個典型的 MVC 應用的用例:
// 一個典型的 MVC 應用的流程大體上是這樣的:
// 1) 用戶點擊按鈕 A
// 2) 點擊按鈕 A 的處理程序觸發 Model A 的改變
// 3) Model A 的改變處理程序觸發 Model B 的改變
// 4) Model B 的改變處理程序觸發 View B 的改變並從新渲染自身web
// 在這樣的一個環境裏,當應用出錯的時候快速地定位 bug 來源是一件很是困難的事情。
// 這是由於每一個 View 能夠監視任何的 Model,
// 而且每一個 Model 能夠監視其它全部 Model,因此數據會從四面八方涌來,而且被許多源(view 或者 model)改變。編程
// 當咱們用 flux 以及它的單向數據流的時候,上面的例子就會變成這樣子:
// 1) 用戶點擊按鈕 A
// 2) 點擊按鈕A的處理程序會觸發一個被分發的 action,並改變 Store A
// 3) 由於其它的 Store 也被這個 action 通知了,因此 Store B 也會對相同的 action 作出反應
// 4) View B 由於 Store A 和 Store B 的改變而收到通知,並從新渲染redux
// 來看一下咱們是如何避免 Store A 和 Store B 直接相關聯的。
// Store 只能被 action 修改,別無他選。
// 而且當全部 Store 響應了 action 後,View 纔會最終更新。因而可知,數據老是沿着一個方向進行流動:
// action -> store -> view -> action -> store -> view -> action -> ...api
// 上面咱們首先從 action 開始咱們的用例,
// 下面讓咱們一樣以 action 和 action creator 來開始咱們的教程。
// 章節 1 - simple-action-creator.js
// 咱們在前言中已經簡單提到過 action,但具體什麼是 action creator,它們又是如何關聯到 action 的呢?
// 其實,經過幾行簡單的代碼就能夠解釋清楚了!
// action creator 就是函數而已...
var actionCreator = function() {
// ...負責構建一個 action (是的,action creator 這個名字已經很明顯了)並返回它
return {
type: 'AN_ACTION'
}
}
// 這就完了?是的,僅此而已。
// 然而,有一件事情須要注意,那就是 action 的格式。flux 通常約定 action 是一個擁有 type 屬性的對象。
// 而後按 type 決定如何處理 action。固然,action 依舊能夠擁有其餘屬性,你能夠任意存放想要的數據。
// 在後面的章節中,咱們會發現 action creator 實際上能夠返回 action 之外的其餘東西,好比一個函數。
// 這在處理異步時頗有用(更多的內容能夠查閱 dispatch-async-action.js)。
// 咱們能夠直接調用 action creator,如同預期的同樣,咱們會獲得一個 action:
console.log(actionCreator())
// 輸出: { type: 'AN_ACTION' }
// 好了,以上代碼沒有任何問題,卻也毫無用處...
// 在實際的場景中,咱們須要的是將 action 發送到某個地方,讓關心它的人知道發生了什麼,而且作出相應的處理。
// 咱們將這個過程稱之爲「分發 action(Dispatching an action)」。
// 爲了分發 action,咱們須要...一個分發函數(= ̄ω ̄=)。
// 而且,爲了讓任何對它感興趣的人都能感知到 action 發起,咱們還須要一個註冊「處理器(handlers)」的機制。
// 這些 action 的「處理器」在傳統的 flux 應用中被稱爲 store,在下個章節中,咱們會介紹它們在 Redux 中叫什麼。
// 至止,咱們的應用中包含了如下流程:
// ActionCreator -> Action
// 章節 2 - about-state-and-meet-redux.js
// 在實際應用中,咱們不只須要 action 告訴咱們發生了什麼,還要告訴咱們須要隨之更新數據。
// 這就讓咱們的應用變的棘手:
// 如何在應用程序的整個生命週期內維持全部數據?
// 如何修改這些數據?
// 如何把數據變動傳播到整個應用程序?
// 因而 Redux 登場。
// Redux (https://github.com/reactjs/redux) 是一個「可預測化狀態的 JavaScript 容器」。
// 咱們先回顧上述提出的問題並用 Redux 的詞彙表給出如下解答(部分詞彙也來源於 Flux):
// 如何在應用程序的整個生命週期內維持全部數據?
// 以你想要的方式維持這些數據,例如 JavaScript 對象、數組、不可變數據,等等。
// 咱們把應用程序的數據稱爲狀態。這是有道理的,由於咱們所說的數據會隨着時間的推移發生變化,這其實就是應用的狀態。
// 可是咱們把這些狀態信息轉交給了 Redux(還記得麼?Redux 就是一個「容納狀態的容器」)。
// 如何修改這些數據?
// 咱們使用 reducer 函數修改數據(在傳統的 Flux 中咱們稱之爲 store)。
// Reducer 函數是 action 的訂閱者。
// Reducer 函數只是一個純函數,它接收應用程序的當前狀態以及發生的 action,而後返回修改後的新狀態(或者有人稱之爲歸併後的狀態)。
// 如何把數據變動傳播到整個應用程序?
// 使用訂閱者來監聽狀態的變動狀況。
// Redux 幫你把這些鏈接起來。
// 總之 Redux 提供了:
// 1)存放應用程序狀態的容器
// 2)一種把 action 分發到狀態修改器的機制,也就是 reducer 函數
// 3)監聽狀態變化的機制
// 咱們把 Redux 實例稱爲 store 並用如下方式建立:
/*
import { createStore } from 'redux'
var store = createStore()
*/
// 可是當你運行上述代碼,你會發現如下異常消息:
// Error: Invariant Violation: Expected the reducer to be a function.
// 這是由於 createStore 函數必須接收一個可以修改應用狀態的函數。
// 咱們再試一下
import { createStore } from 'redux'
var store = createStore(() => {})
// 看上去沒有問題了...
// 章節 3 - simple-reducer.js
// 如今,咱們知道如何去建立一個 Redux 實例,並讓它管理應用中的 state
// 下面講一下這些 reducer 函數是如何轉換 state 的。
// Reducer 與 Store 區別:
// 你可能已經注意到,在簡介章節中的 Flux 圖表中,有 Store,但沒有
// Redux 中的 Reducer。那麼,Store 與 Reducer 到底有哪些區別呢?
// 實際的區別比你想象的簡單:Store 能夠保存你的 data,而 Reducer 不能。
// 所以在傳統的 Flux 中,Store 自己能夠保存 state,但在 Redux 中,每次調用 reducer
// 時,都會傳入待更新的 state。這樣的話,Redux 的 store 就變成了
// 「無狀態的 store」 而且改了個名字叫 Reducer。
// 如上所述,在建立一個 Redux 實例前,須要給它一個 reducer 函數...
import { createStore } from 'redux'
var store_0 = createStore(() => {})
// ...因此每當一個 action 發生時,Redux 都能調用這個函數。
// 往 createStore 傳 Reducer 的過程就是給 Redux 綁定 action 處理函數(也就是 Reducer)的過程。
// action 處理函數在 01_simple-action-creator.js 章節中有討論過。
// 在 Reducer 中打印一些 log
var reducer = function (...args) {
console.log('Reducer was called with args', args)
}
var store_1 = createStore(reducer)
// 輸出:Reducer was called with args [ undefined, { type: '@@redux/INIT' } ]
// 看出來了嗎?咱們的 reducer 被調用了,但咱們並無 dispatch 任何 action...
// 這是由於在初始化應用 state 的時候,
// Redux dispatch 了一個初始化的 action ({ type: '@@redux/INIT' })
// 在被調用時,一個 reducer 會獲得這些參數:(state, action)
// 在應用初始化時,state 還沒被初始化,所以它的值是 "undefined",
// 這是很是符合邏輯的
// 在處理 「init」 action 以後,咱們應用中的 state 又會是怎麼樣的呢?
// 章節 4 - get-state.js
// 如何從 Redux 實例中讀取 state ?
import { createStore } from 'redux'
var reducer_0 = function (state, action) {
console.log('reducer_0 was called with state', state, 'and action', action)
}
var store_0 = createStore(reducer_0)
// 輸出: reducer_0 was called with state undefined and action { type: '@@redux/INIT' }
// 爲了讀取 Redux 保存的 state,你能夠調用 getState
console.log('store_0 state after initialization:', store_0.getState())
// 輸出: store_0 state after initialization: undefined
// 都已經初始化過了,難道程序的 state 仍是 undefined 的?沒錯,正是如此,
// 到目前爲止,咱們的 reducer 還什麼事都沒作過…… 你是否還有印象,咱們在 "about-state-and-meet-redux" 那一章裏是怎麼描述一個 reducer 的預期行爲的?
// 「一個 reducer 只是一個函數,它能收到程序當前的 state 與 action,
// 而後返回一個 modify(又或者學別人同樣稱之爲 reduce )過的新 state 」
// 咱們的 reducer 目前什麼都不返回,因此程序的 state 固然只能是 reducer() 返回的那個叫 「undefined」 的東西。
// 接下來,咱們試着在 reducer 收到 undefined 的 state 時,給程序發一個初始狀態:
var reducer_1 = function (state, action) {
console.log('reducer_1 was called with state', state, 'and action', action)
if (typeof state === 'undefined') {
return {}
}
return state;
}
var store_1 = createStore(reducer_1)
// 輸出:reducer_1 was called with state undefined and action { type: '@@redux/INIT' }
console.log('store_1 state after initialization:', store_1.getState())
// 輸出:store_1 state after initialization: {}
// 如咱們所願,如今 Redux 初始化之後返回的 state 變成 {} 了
//
// 感謝ES6,這個模式如今實現起來很清晰:
var reducer_2 = function (state = {}, action) {
console.log('reducer_2 was called with state', state, 'and action', action)
return state;
}
var store_2 = createStore(reducer_2)
// 輸出: reducer_2 was called with state {} and action { type: '@@redux/INIT' }
console.log('store_2 state after initialization:', store_2.getState())
// 輸出: store_2 state after initialization: {}
// 估計你已經發現了,咱們給 reducer_2 的 state 參數傳了默認值以後,
// reducer 就不會再取到 undefined 的 state 了。
// 小結一下:調用 reducer ,只是爲了響應一個派發來的 action 。
// 接下來,咱們在 response 裏模擬一個 state 修改,其響應的 action 類型是 'SAY_SOMETIHG'
var reducer_3 = function (state = {}, action) {
console.log('reducer_3 was called with state', state, 'and action', action)
switch (action.type) {
case 'SAY_SOMETHING':
return {
...state,
message: action.value
}
default:
return state;
}
}
var store_3 = createStore(reducer_3)
// 輸出: reducer_3 was called with state {} and action { type: '@@redux/INIT' }
console.log('store_3 state after initialization:', store_3.getState())
// 輸出: store_3 state after initialization: {}
// 到目前爲止,咱們都尚未獲得一個新 state, 由於咱們尚未真的派發過任何 action 。
// 不過在最後一個例子裏,有幾個點值得注意:
//
// 0) 我假設了 action 裏必定包含了一個 type 跟一個 value 。type 基本上是 flux action 已經約定俗成的,
// 而 value 屬性能夠是任何類型的。
// 1) 這裏有個常見模式:在 reducer 裏用 switch 來響應對應的 action 。
// 2) 用 switch 的時候, **永遠** 不要忘記放個 「default」 來返回 「state」,不然,
// 你的 reducer 可能會返回 「undefined」 (等於你的 state 就丟了)
// 3) 注意 { message: action.value } 是怎麼被合併到當前 state 來造成新 state 的,
// 這全要感謝牛逼的 ES7 notation (Object Spread): { ...state, message: action.value }
// 4) 還要注意:之因此這個例子能用ES7 Object Spread notation ,是由於它只對 state 裏的
// { message: action.value} 作了淺拷貝(也就是說, state 第一個層級的屬性直接被 { message: action.value } 覆蓋掉了 —— 與之相對,其實也有優雅的合併方式 )
// 可是若是數據結構更復雜或者是嵌套的,那處理state更新的時候,極可能還須要考慮一些徹底不一樣的作法:
// - 能夠考慮: Immutable.js (https://facebook.github.io/immutable-js/)
// - 能夠考慮: Object.assign (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
// - 能夠考慮: 手工合併
// - 又或者考慮用其它任何能知足須要且適合 state 結構的方法,Redux 對此是全無預設的方式的(要記得 Redux 只是個狀態的容器)。
// 如今開始,咱們要在 reducer 裏處理 action 了,咱們將會有多個 reducer 並會組合它們。
// 章節 5 - combine-reducers.js
// 咱們如今來看一下什麼是 reducer
var reducer_0 = function (state = {}, action) {
console.log('reducer_0 was called with state', state, 'and action', action)
switch (action.type) {
case 'SAY_SOMETHING':
return {
...state,
message: action.value
}
default:
return state;
}
}
// 在繼續以前,咱們先來想象一下擁有不少 action 的 reducer 長什麼樣子
var reducer_1 = function (state = {}, action) {
console.log('reducer_1 was called with state', state, 'and action', action)
switch (action.type) {
case 'SAY_SOMETHING':
return {
...state,
message: action.value
}
case 'DO_SOMETHING':
// ...
case 'LEARN_SOMETHING':
// ...
case 'HEAR_SOMETHING':
// ...
case 'GO_SOMEWHERE':
// ...
// etc.
default:
return state;
}
}
// 很顯然,只有一個 reducer 是 hold 不住咱們整個應用中全部 action 操做的(好吧,事實上它能 hold 得住,
// 但這會變得很難維護。)
// 幸運的是,Redux 不關心咱們究竟是隻有一個 reducer ,仍是有一打(12個)reducer 。
// 若是咱們有多個 reducer ,Redux 能幫咱們合併成一個。
// 讓咱們來定義 2 個 reducer
var userReducer = function (state = {}, action) {
console.log('userReducer was called with state', state, 'and action', action)
switch (action.type) {
// etc.
default:
return state;
}
}
var itemsReducer = function (state = [], action) {
console.log('itemsReducer was called with state', state, 'and action', action)
switch (action.type) {
// etc.
default:
return state;
}
}
// 我但願你特別留意賦給每一個 reducer 的初始 state :
// 1. 賦給 userReducer 的初始 state 是一個空對象,即 {}
// 2. 賦給 itemsReducer 的初始 state 是一個空數組,即 []
// 賦予不一樣類型的值是爲了說明 reducer 是能夠處理任何類型的數據結構的。你徹底能夠選擇那些符合你的需求的
// 數據結構做爲 state 的值。(例如,字面量對象、數組、布爾值、字符串或其它不可變結構)
// 在這種多個 reducer 的模式下,咱們可讓每一個 reducer 只處理整個應用的部分 state 。
// 但咱們須要知道,createStore 只接收一個 reducer 函數。
// 那麼,咱們怎麼合併全部的 reducer? 咱們又該如何告訴 Redux 每一個 reducer 只處理一部分 state 呢?
// 其實這很簡單。咱們使用 combineReducers 輔助函數。
// combineReducers 接收一個對象並返回一個函數,當 combineReducers 被調用時,它會去調用每一個
// reducer,並把返回的每一塊 state 從新組合成一個大 state 對象(也就是 Redux 中的 Store)。
// 長話短說,下面演示一下如何使用多個 reducer 建立一個 Redux 實例:
import { createStore, combineReducers } from 'redux'
var reducer = combineReducers({
user: userReducer,
items: itemsReducer
})
// 輸出:
// userReducer was called with state {} and action { type: '@@redux/INIT' }
// userReducer was called with state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_9.r.k.r.i.c.n.m.i' }
// itemsReducer was called with state [] and action { type: '@@redux/INIT' }
// itemsReducer was called with state [] and action { type: '@@redux/PROBE_UNKNOWN_ACTION_4.f.i.z.l.3.7.s.y.v.i' }
var store_0 = createStore(reducer)
// 輸出:
// userReducer was called with state {} and action { type: '@@redux/INIT' }
// itemsReducer was called with state [] and action { type: '@@redux/INIT' }
// 正如你從輸出中看到的,每一個 reducer 都被正確地調用了(但接收了個 init action @@redux/INIT )。
// 這個 action 是什麼鬼?這是 combineReducers 實施的一次安全檢查,用以確保 reducer 永遠不會返回
// undefined。請注意,在 combineReducers 中第一次調用 init action 時,實際上是隨機 action 來的,
// 但它們有個共同的目的 (便是作一個安全檢查)。
console.log('store_0 state after initialization:', store_0.getState())
// 輸出:
// store_0 state after initialization: { user: {}, items: [] }
// 有趣的是,咱們發現 Redux 正確處理了 state 的各個部分。最終的 state 徹底是一個簡單的對象,由
// userReducer 和 itemsReducer 返回的部分 state 共同組成。
// {
// user: {}, // {} is the slice returned by our userReducer
// items: [] // [] is the slice returned by our itemsReducer
// }
// 因爲咱們爲每一個 reducer 初始化了一個特殊的值(userReducer 的是空對象 {} ,itemsReducer 的是空數
// 組 [] ),因此在最終 Redux 的 state 中找到那些值並非巧合。
// 如今,關於 reducer 如何工做咱們已經有了清楚的理解。是時候去看看當 action 被分發(dispatch)時會對
// Redux 的 state 有什麼影響。
// 章節 6 - dispatch-action.js
// 迄今爲止咱們的關注點都是綁定咱們的 reducer,但咱們還未 dispatch 任何一個 action。
// 咱們將會用到上一章的 reducer ,並用它們處理一些 action:
var userReducer = function (state = {}, action) {
console.log('userReducer was called with state', state, 'and action', action)
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.name
}
default:
return state;
}
}
var itemsReducer = function (state = [], action) {
console.log('itemsReducer was called with state', state, 'and action', action)
switch (action.type) {
case 'ADD_ITEM':
return [
...state,
action.item
]
default:
return state;
}
}
import { createStore, combineReducers } from 'redux'
var reducer = combineReducers({
user: userReducer,
items: itemsReducer
})
var store_0 = createStore(reducer)
console.log("\n", '### It starts here')
console.log('store_0 state after initialization:', store_0.getState())
// 輸出:
// store_0 state after initialization: { user: {}, items: [] }
// 讓咱們來 dispatch 咱們的第一個 action... 記住在 'simple-action-creator.js' 中所提到的:
// "爲了 dispatch 一個 action,咱們須要一個 dispatch 函數。"
// 咱們所看到的 dispatch 函數,是 Redux 提供的,而且它會將 action 傳遞
// 給任何一個 reducer!dispatch 函數本質上是 Redux
// 的實例的屬性 "dispatch"
// dispatch 一個 action:
store_0.dispatch({
type: 'AN_ACTION'
})
// 輸出:
// userReducer was called with state {} and action { type: 'AN_ACTION' }
// itemsReducer was called with state [] and action { type: 'AN_ACTION' }
// 每個 reducer 都被調用了,可是沒有一個 action type 是 reducer 須要的,
// 所以 state 是不會發生變化的:
console.log('store_0 state after action AN_ACTION:', store_0.getState())
// 輸出:store_0 state after action AN_ACTION: { user: {}, items: [] }
// 可是,等一下!咱們是否是能夠用一個 action creator 去發送一個 action?咱們確實能夠
// 用一個 actionCreator,但因爲它只是返回一個 action,那麼就意味着它不會攜帶任何東西
// 到這個例子中。但爲了面對將來遇到的困難,咱們仍是以正確的方式,
// 即以 flux 理論去作吧。讓咱們使用這個 action creator 發送一個咱們想要的 action:
var setNameActionCreator = function (name) {
return {
type: 'SET_NAME',
name: name
}
}
store_0.dispatch(setNameActionCreator('bob'))
// 輸出:
// userReducer was called with state {} and action { type: 'SET_NAME', name: 'bob' }
// itemsReducer was called with state [] and action { type: 'SET_NAME', name: 'bob' }
console.log('store_0 state after action SET_NAME:', store_0.getState())
// 輸出:
// store_0 state after action SET_NAME: { user: { name: 'bob' }, items: [] }
// 咱們剛剛處理了一個 action,而且它改變了應用的 state!
// 可是這彷佛太簡單了,而且還不足以充當一個真實的用例。例如,
// 若是咱們要在 dispatch action 以前作一些異步的操做,那應該怎麼作呢?
// 咱們將在下一章節 "dispatch-async-action.js" 中討論這個問題
// 至止,咱們接觸的應用流程是這樣的:
// ActionCreator -> Action -> dispatcher -> reducer
// 章節 7 - dispatch-async-action-1.js
// 在上節教程中咱們知道了如何分發 action 以及這些 action 如何經過 reducer 函數修改應用狀態。
// 可是,到目前爲止,咱們只考慮了一種狀況,同步場景下的 action,準確地說是同步 action creator,它建立同步的 action,
// 也就是當 action creator 被調用時,action 會被當即返回。
// 咱們來設想一個簡單的異步場景:
// 1)用戶點擊「Say Hi in 2 seconds」按鈕
// 2)當用戶點擊按鈕 A,咱們但願通過兩秒,視圖顯示一條消息 Hi
// 3)兩秒過去以後,更新視圖,顯示消息 Hi
// 固然這條消息是應用的狀態之一,因此咱們必然將其存儲於 Redux store。
// 可是咱們但願的結果是,在調用 action creator 的兩秒以後才把消息存入 store(由於若是當即更新狀態,
// 那麼就會當即觸發全部監聽狀態變動的訂閱者 —— 例如視圖,致使消息早於兩秒顯示)。
// 若是咱們按照目前調用 action creator 的方式...
import { createStore, combineReducers } from 'redux'
var reducer = combineReducers({
speaker: function (state = {}, action) {
console.log('speaker was called with state', state, 'and action', action)
switch (action.type) {
case 'SAY':
return {
...state,
message: action.message
}
default:
return state;
}
}
})
var store_0 = createStore(reducer)
var sayActionCreator = function (message) {
return {
type: 'SAY',
message
}
}
console.log("\n", 'Running our normal action creator:', "\n")
console.log(new Date());
store_0.dispatch(sayActionCreator('Hi'))
console.log(new Date());
console.log('store_0 state after action SAY:', store_0.getState())
// 輸出(忽略初始輸出):
// Sun Aug 02 2015 01:03:05 GMT+0200 (CEST)
// speaker was called with state {} and action { type: 'SAY', message: 'Hi' }
// Sun Aug 02 2015 01:03:05 GMT+0200 (CEST)
// store_0 state after action SAY: { speaker: { message: 'Hi' } }
// ... 結果 store 被當即更新了。
// 咱們但願看到的結果應該相似於下面這樣的代碼:
var asyncSayActionCreator_0 = function (message) {
setTimeout(function () {
return {
type: 'SAY',
message
}
}, 2000)
}
// 可是這樣 action creator 返回的不是 action 而是 undefined。因此這並非咱們所指望的解決方法。
// 這裏有個訣竅:不返回 action,而是返回 function。這個 function 會在合適的時機 dispatch action。可是若是咱們但願
// 這個 function 可以 dispatch action,那麼就須要向它傳入 dispatch 函數。因而代碼相似以下:
var asyncSayActionCreator_1 = function (message) {
return function (dispatch) {
setTimeout(function () {
dispatch({
type: 'SAY',
message
})
}, 2000)
}
}
// 你可能再次注意到 action creator 返回的不是 action 而是 function。
// 因此 reducer 函數極可能不知道如何處理這樣的返回值,而你也並不清楚是否可行,那麼讓咱們一塊兒再作嘗試,一探究竟。
// 章節 8 - dispatch-async-action-2.js
// 運行以前咱們在 dispatch-async-action-1.js 中實現的第一個異步 action creator:
import { createStore, combineReducers } from 'redux'
var reducer = combineReducers({
speaker: function (state = {}, action) {
console.log('speaker was called with state', state, 'and action', action)
switch (action.type) {
case 'SAY':
return {
...state,
message: action.message
}
default:
return state;
}
}
})
var store_0 = createStore(reducer)
var asyncSayActionCreator_1 = function (message) {
return function (dispatch) {
setTimeout(function () {
dispatch({
type: 'SAY',
message
})
}, 2000)
}
}
console.log("\n", 'Running our async action creator:', "\n")
store_0.dispatch(asyncSayActionCreator_1('Hi'))
// 輸出:
// ...
// /Users/classtar/Codes/redux-tutorial/node_modules/redux/node_modules/invariant/invariant.js:51
// throw error;
// ^
// Error: Invariant Violation: Actions must be plain objects. Use custom middleware for async actions.
// ...
// 咱們所設計的 function 彷佛沒有進入 reducer 函數。可是 Redux 給出了舒適提示:使用自定義中間件(middleware)來支持異步 action。
// 看來咱們的方向是正確的,可中間件(middleware)又是什麼呢?
// 我向你保證 action creator asyncSayActionCreator_1 不只沒有問題,並且只要咱們搞清楚 middleware 的概念並掌握它的使用方法,
// 這個異步 action creator 就會按照咱們所設想的結果執行。
// 開始下節教程:09_middleware.js
// 章節 9 - middleware.js
// 在 dispatch-async-action-2.js 章節中咱們拋出了「中間件」的概念。中間件彷佛
// 能夠幫助咱們處理異步 action。但中間件究竟是什麼呢?
// 一般來講中間件是在某個應用中 A 和 B 部分中間的那一塊,
// 中間件能夠把 A 發送數據到 B 的形式從
// A -----> B
// 變成:
// A ---> middleware 1 ---> middleware 2 ---> middleware 3 --> ... ---> B
// 那麼中間件在 Redux 中是如何工做的?
// 看上去 Redux 並不能自動處理 action creator 中返回的異步函數。
// 但若是在 action creator 和 reducer 之間增長一箇中間件,就能夠把這個函數轉成
// 適合 Redux 處理的內容:
// action ---> dispatcher ---> middleware 1 ---> middleware 2 ---> reducers
// 每當一個 action(或者其餘諸如異步 action creator 中的某個函數)被分發時,
// 咱們的中間件就會被調用
// 而且在須要的時候協助 action creator 分發真正的 action(或者什麼都不作,
// 有時咱們須要這麼作)
// 在 Redux 中,中間件是純粹的函數,
// 有明確的使用方法而且嚴格的遵循如下格式:
/*
var anyMiddleware = function ({ dispatch, getState }) {
return function(next) {
return function (action) {
// 你的中間件業務相關代碼
}
}
}
*/
// 如上所述,中間件由三個嵌套的函數構成(會依次調用):
// 1) 第一層向其他兩層提供分發函數和 getState 函數
// (由於你的中間件或 action creator 可能須要從 state 中讀取數據)
// 2) 第二層提供 next 函數,它容許你顯式的將處理過的輸入傳遞給下一個中間件或 Redux
// (這樣 Redux 才能調用全部 reducer)。
// 3) 第三層提供從上一個中間件或從 dispatch 傳遞來的 action,
// 這個 action 能夠調用下一個中間件(讓 action 繼續流動) 或者
// 以想要的方式處理 action。
// 學習過函數式編程的人可能會意識到給上述代碼提供了一個機會來使用
// 柯里化(若是你不理解也不要緊,跳過接下去的 10 行,不會影響你對 redux 的理解)。
// 使用柯里化,你能夠簡化上述函數:
/*
// "curry" may come any functional programming library (lodash, ramda, etc.)
var thunkMiddleware = curry(
({dispatch, getState}, next, action) => (
// 你的中間件業務相關代碼
)
);
*/
// 咱們爲異步 action creator 提供的中間件叫 thunk middleware
// 它的代碼在:https://github.com/gaearon/redux-thunk.
// 它看上去是這樣 (爲了可讀性使用 ES5 語法書寫該函數):
var thunkMiddleware = function ({ dispatch, getState }) {
// console.log('Enter thunkMiddleware');
return function(next) {
// console.log('Function "next" provided:', next);
return function (action) {
// console.log('Handling action:', action);
return typeof action === 'function' ?
action(dispatch, getState) :
next(action)
}
}
}
// 爲了讓 Redux 知道咱們有一個或多箇中間件,咱們使用 Redux 的
// 輔助函數:applyMiddleware.
// applyMiddleware 接收全部中間件做爲參數,返回一個供 Redux createStore 調用的函數。
// 當最後這個函數被調用時,它會產生一個 Store 加強器,用來將全部中間件應用到 Store 的 dispatch 上。
// (來自 https://github.com/rackt/redux/blob/v1.0.0-rc/src/utils/applyMiddleware.js)
// 下面就是如何將一箇中間件應用到 Redux store:
import { createStore, combineReducers, applyMiddleware } from 'redux'
const finalCreateStore = applyMiddleware(thunkMiddleware)(createStore)
// 針對多箇中間件, 使用:applyMiddleware(middleware1, middleware2, ...)(createStore)
var reducer = combineReducers({
speaker: function (state = {}, action) {
console.log('speaker was called with state', state, 'and action', action)
switch (action.type) {
case 'SAY':
return {
...state,
message: action.message
}
default:
return state
}
}
})
const store_0 = finalCreateStore(reducer)
// 輸出:
// speaker was called with state {} and action { type: '@@redux/INIT' }
// speaker was called with state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_s.b.4.z.a.x.a.j.o.r' }
// speaker was called with state {} and action { type: '@@redux/INIT' }
// 如今 store 的 middleware 已經準備好了,再來嘗試分發咱們的異步 action:
var asyncSayActionCreator_1 = function (message) {
return function (dispatch) {
setTimeout(function () {
console.log(new Date(), 'Dispatch action now:')
dispatch({
type: 'SAY',
message
})
}, 2000)
}
}
console.log("\n", new Date(), 'Running our async action creator:', "\n")
store_0.dispatch(asyncSayActionCreator_1('Hi'))
// 輸出:
// Mon Aug 03 2015 00:01:20 GMT+0200 (CEST) Running our async action creator:
// Mon Aug 03 2015 00:01:22 GMT+0200 (CEST) 'Dispatch action now:'
// speaker was called with state {} and action { type: 'SAY', message: 'Hi' }
// 當咱們調用異步 action creator 兩秒以後,action 成功被分發出去。
// 你可能會好奇,一箇中間件如何 log 出全部已分發的 action ,
// 是這樣:
function logMiddleware ({ dispatch, getState }) {
return function(next) {
return function (action) {
console.log('logMiddleware action received:', action)
return next(action)
}
}
}
// 一樣的,下面是一箇中間件,它會丟棄全部通過的 action(不是很實用,
// 可是若是加一些判斷就能實現丟棄一些 action,放到一些 action 給下一個中間件):
function discardMiddleware ({ dispatch, getState }) {
return function(next) {
return function (action) {
console.log('discardMiddleware action received:', action)
}
}
}
// 經過使用 logMiddleware 或 discardMiddleware 試着修改上述的 finalCreateStore 調用
// 看看會發生什麼...
// 好比,這樣用:
// const finalCreateStore = applyMiddleware(discardMiddleware, thunkMiddleware)(createStore)
// 會讓你的 action 永遠沒法到達 thunkMiddleware 和 reducer。
// 查看 http://rackt.org/redux/docs/introduction/Ecosystem.html 的中間件部分能夠了解其餘例子。
// 總結一下到目前爲止咱們所學的:
// 1) 咱們知道怎樣寫 action 和 action creator
// 2) 咱們知道怎樣分發 action
// 3) 咱們知道怎樣使用中間件處理自定義 action,好比異步 action
// 對於 Flux 體系的完整閉環,咱們還剩下惟一的一塊就是如何訂閱 state 的更新
// 並響應這些更新(好比從新渲染咱們的組件)
// 因此咱們怎麼訂閱 Redux store 的更新呢?
// 繼續下一個教程: 10_state-subscriber.js
// 章節 10 - state-subscriber.js
// 咱們接近完成一個完整的 Flux 閉環了,如今只差一個相當重要的環節:
// 沒有它,在 store 改變時咱們就不能更新咱們的視圖。
// 幸運的是,監視 Redux store 更新有一個很簡單的辦法:
/*
store.subscribe(function() {
// retrieve latest store state here
// Ex:
console.log(store.getState());
})
*/
// 是的,簡單到咱們都開始從新相信聖誕老人了(譯者注:2333,對不起這個比喻太幽默了)
// 試一下這段代碼:
import { createStore, combineReducers } from 'redux'
var itemsReducer = function (state = [], action) {
console.log('itemsReducer was called with state', state, 'and action', action)
switch (action.type) {
case 'ADD_ITEM':
return [
...state,
action.item
]
default:
return state;
}
}
var reducer = combineReducers({ items: itemsReducer })
var store_0 = createStore(reducer)
store_0.subscribe(function() {
console.log('store_0 has been updated. Latest store state:', store_0.getState());
// 在這裏更新你的視圖
})
var addItemActionCreator = function (item) {
return {
type: 'ADD_ITEM',
item: item
}
}
store_0.dispatch(addItemActionCreator({ id: 1234, description: 'anything' }))
// 輸出:
// ...
// store_0 has been updated. Latest store state: { items: [ { id: 1234, description: 'anything' } ] }
// 咱們的訂閱回調成功的調用了,同時 store 如今包含了咱們新增的條目。
// 理論上,到這就能夠中止了。咱們的 Flux loop 已經閉合,咱們理解了構造 Flux 的所有概念,
// 實際上它也沒那麼神祕。可是老實說,還有不少要講的,
// 爲了讓最後一個概念保持簡單,
// 咱們有意的在例子中去掉了一些東西:
// - 咱們的訂閱回調沒有把 state 做爲參數,爲何?
// - 既然咱們沒有接受新的 state, 咱們就被限定到了只能開發這個已經完成的 store (store_0) 因此這個辦法在
// 含有多個模塊的應用下不可行。
// - 咱們到底是怎麼更新視圖的?
// - 怎麼取消訂閱?
// - 更通俗的講,咱們怎麼把 Redux 和 React 結合到一塊兒?
// 咱們如今進入了一個「將 Redux 加入到 React」 的領域。
// 理解 Redux 能夠無條件綁定到 React 上是很重要的。
// Redux 是一個「爲 Javascript 應用而生的可預測的狀態容器」,
// 你有不少方式去使用它,而 React 應用只不過是其中一個。
// 從這個角度看,若是沒有 react-redux (https://github.com/reactjs/react-redux),咱們將失去不少。
// 在 Redux 1.0.0 以前它是包含在 Redux 中的,這個庫節省了咱們不少時間,
// 它包含了在 React 中使用 Redux 時全部的綁定。
// 回到訂閱這件事,爲何咱們這個訂閱函數看上去很是簡單
// 並且沒有提供不少特性?
// 這就是 Redux 精彩之處了! 它全部 API 都很抽象(包括訂閱),
// 支持高度擴展,容許開發者造出一些瘋狂的輪子
// 好比 Redux DevTools (https://github.com/gaearon/redux-devtools).
// 可是最後咱們仍是須要一個更好的接口訂閱咱們的 store 變化。這也就是 react-redux 給帶給咱們的:
// 一個完美填補原生 Redux 訂閱機制和開發者的期待之間的空缺的 API ,
// 這樣咱們再也不須要直接使用訂閱。而只是
// 使用 「provide」 和 「connect」 綁定,沒必要再關心隱含在內的訂閱方法。
// 因此,訂閱方法依然會被咱們使用,
// 只不過它經過高度整合的接口替咱們處理 redux state 的鏈接。
// 如今咱們隱藏了那些綁定,而且展現了鏈接你的組件和 Redux 的 state 是很輕鬆的一件事。
// 繼續下一個教程: 11_Provider-and-connect.js
// 章節 11 - Provider-and-connect.js
// 這實際上是教程的最後一章,一塊兒聊聊如何把 Redux 和 React 綁定在一塊兒。
// 要運行下面的示例,你須要一個瀏覽器。
// 本示例中的代碼和註釋都在 ./11_src/src/ 目錄下。
// 當你讀到下面這段話的時間,請運行 11_src/src/server.js。
// 開發一個 React 應用和服務器來讓瀏覽器能夠訪問,咱們會用到:
// - 用 node HTTP(https://nodejs.org/api/http.html) 建立一個很是簡單的服務器
// - 用 Webpack 去打包咱們的應用,
// - 神奇的 Webpack Dev Server (http://webpack.github.io/docs/webpack-dev-server.html)
// 做爲一個專門的 node 服務器,並監聽 JS 改變自動編譯
// - 超棒的 React Hot Loader http://gaearon.github.io/react-hot-loader/ (Dan Abramov
// 開發的另外一個很棒的項目,沒錯,他就是 Redux 的做者) ,提供很是棒的
// DX (開發體驗) ,當咱們在編輯器中修改代碼時,
// 在瀏覽器中能夠熱加載來顯示效果。
// 提醒一下正在使用 React 的開發者:本應用是基於 React 0.14 構建的
// 我不想在這裏詳細地解釋如何設置 Webpack Dev Server 和 React Hot Loader,
// 由於在 React Hot Loader 的文檔中已經說的很好了。
import webpackDevServer from './11_src/src/webpack-dev-server'
// 咱們應用啓動的主要服務器請求都是來自這個文件。
import server from './11_src/src/server'
// 若是 5050 端口號已經被佔用了,那麼就修改下面的端口號。
// 若是端口號是 X,那麼咱們能夠用 X 做爲服務器的端口號,用 X+1 做爲 webpack-dev-server 的端口號
const port = 5050
// 啓動 webpack dev server...
webpackDevServer.listen(port)
// ... 還有主應用服務器。
server.listen(port)
console.log(`Server is listening on http://127.0.0.1:${port}`)
// 轉到 11_src/src/server.js...
https://github.com/react-guide/redux-tutorial-cn