爲了幫助你們快速上手什麼是Redux開發,在這本節中將向你們介紹什麼是Redux開發所須要的一些什麼是Redux必備基礎以及高級知識。html
Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理,可讓你構建一致化的應用,運行於不一樣的環境(客戶端、服務器、原生應用),而且易於測試。react
咱們過下整個工做流程:git
到這兒爲止,一次用戶交互流程結束。能夠看到,在整個流程中數據都是單向流動的。github
Redux是Flux思想的一種實現,同時又在其基礎上作了改進。Redux秉承了Flux單向數據流、Store是惟一的數據源的思想。數據庫
Redux和Flux的最大不一樣是Redux沒有 Dispatcher 且不支持多個 store。Redux只有一個單一的 store 和一個根級的 reduce 函數(reducer),隨着應用不斷變大,咱們須要將根級的 reducer 拆成多個小的 reducers,分別獨立地操做 state 樹的不一樣部分,而不是添加新的 stores。npm
隨着 JavaScript 應用愈來愈大,愈來愈複雜,咱們須要管理的state變得愈來愈多。 這些 state 可能包括服務器響應、緩存數據、本地生成還沒有持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。json
管理不斷變化的 state 很是困難。若是一個 model 的變化會引發另外一個 model 變化,那麼當 view 變化時,就可能引發對應 model 以及另外一個 model 的變化,依次地,可能會引發另外一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在何時,因爲什麼緣由,如何變化已然不受控制。 當系統變得錯綜複雜的時候,想重現問題或者添加新功能就會變得很是複雜。redux
雖然React 試圖在視圖層禁止異步和直接操做 DOM 來解決這個問題。美中不足的是,React 依舊把處理 state 中數據的問題留給了你。Redux就是爲了幫你解決這個問題。react-native
Redux應用中全部的 state 都以一個對象樹的形式儲存在一個單一的 store 中。 唯一改變 state 的辦法是觸發 action,action就是一個描述發生什麼的對象。 爲了描述 action 如何改變 state 樹,你須要編寫 reducers。數組
先看一個redux的簡單使用例子:
import { createStore } from 'redux';
// 建立Redux reducer
/**
* 這是一個 reducer,形式爲 (state, action) => state 的純函數。
* 描述了 action 如何把 state 轉變成下一個 state。
*
* state 的形式取決於你,能夠是基本類型、數組、對象,
* 當 state 變化時須要返回全新的對象,而不是修改傳入的參數。
*
* 下面例子使用 `switch` 語句和字符串來作判斷,但你能夠寫幫助類(helper)
* 根據不一樣的約定(如方法映射)來判斷,只要適用你的項目便可。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 建立 Redux store 來存放應用的狀態。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 能夠手動訂閱更新,也能夠事件綁定到視圖層。
store.subscribe(() =>
console.log(store.getState())
);
// 改變內部 state 唯一方法是 dispatch 一個 action。
// action 能夠被序列化,用日記記錄和儲存下來,後期還能夠以回放的方式執行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
複製代碼
以上代碼即是一個redux的最簡單的使用,接下來咱們來分別介紹一下redux的三大組成部分:action、reducer以及store。
Action 是把數據從應用傳到 store 的有效載荷。它是 store 數據的惟一來源,也就是說要改變store中的state就須要觸發一個action。
Action 本質上一個普通的JavaScript對象。action 內必須使用一個字符串類型的 type 字段來表示將要執行的動做
,除了 type 字段外,action 對象的結構徹底由你本身決定。多數狀況下,type 會被定義成字符串常量。當應用規模愈來愈大時,建議使用單獨的模塊或文件來存放 action。
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
//action
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
複製代碼
提示:使用單獨的模塊或文件來定義 action type 常量並非必須的,甚至根本不須要定義。對於小應用來講,使用字符串作 action type 更方便些。不過,在大型應用中把它們顯式地定義成常量仍是利大於弊的。
Action 建立函數 就是生成 action 的方法。「action」 和 「action 建立函數」 這兩個概念很容易混在一塊兒,使用時最好注意區分。
在 Redux 中的 action 建立函數只是簡單的返回一個 action:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
複製代碼
這樣作將使 action 建立函數更容易被移植和測試。
reducer是根據action 修改state 將其轉變成下一個 state,記住 actions 只是描述了有事情發生了這一事實,並無描述應用如何更新 state。
(previousState, action) => newState
複製代碼
保持 reducer 純淨很是重要。永遠不要在 reducer 裏作這些操做:
提示:reducer 是純函數。它僅僅用於計算下一個 state。它應該是徹底可預測的:屢次傳入相同的輸入必須產生相同的輸出。它不該作有反作用的操做,如 API 調用或路由跳轉。這些應該在 dispatch action 前發生。
//reducer
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
複製代碼
提示:
Object.assign()
新建了一個副本。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter })
,由於它會改變第一個參數的值。你必須把第一個參數設置爲空對象。你也能夠開啓對ES7提案對象展開運算符的支持, 從而使用 { ...state,visibilityFilter: action.filter }
達到相同的目的。function onAction(state = defaultState, action) {
switch (action.type) {
case Types.THEME_CHANGE://主題
return {
...state,
theme: action.theme,
};
case Types.SHOW_THEME_VIEW://主題
return {
...state,
customThemeViewVisible: action.customThemeViewVisible,
};
case Types.SORT_LANGUAGE://排序
return Object.assign({}, state, {
checkedArray: action.checkedArray,
});
case Types.REFRESH_ABOUT://關於
return Object.assign({}, state, {
[action.flag]: {
...state[action.flag],
projectModels: action.projectModels,
}
});
case Types.ABOUT_SHOW_MORE://關於
return Object.assign({}, state, {
me: {
...state.me,
[action.menuFlag]: action.menuShow
}
});
default:
return state;
}
}
複製代碼
上述代碼看起來有些冗長,而且主題、排序、關於的更新看起來是相互獨立的,能不能將他們拆到單獨的函數或文件裏呢,答案是能夠的。
拆分
//主題 theme.js
export default function onTheme(state = defaultState, action) {
switch (action.type) {
case Types.THEME_CHANGE:
return {
...state,
theme: action.theme,
};
case Types.SHOW_THEME_VIEW:
return {
...state,
customThemeViewVisible: action.customThemeViewVisible,
};
default:
return state;
}
}
//排序 sort.js
export default function onSort(state = defaultState, action) {
switch (action.type) {
case Types.SORT_LANGUAGE:
return Object.assign({}, state, {
checkedArray: action.checkedArray,
});
default:
return state;
}
}
//關於 about.js
export default function onAbout(state = defaultState, action) {
switch (action.type) {
case Types.REFRESH_ABOUT:
return Object.assign({}, state, {
[action.flag]: {
...state[action.flag],
projectModels: action.projectModels,
}
});
case Types.ABOUT_SHOW_MORE:
return Object.assign({}, state, {
me: {
...state.me,
[action.menuFlag]: action.menuShow
}
});
default:
return state;
}
}
複製代碼
在上述代碼中,咱們將對主題、排序、關於的操做拆到了單獨的函數中並放到了不一樣的文件裏,這樣以來各個模塊的操做就更加的聚合了,代碼看起來也就更加的簡潔明瞭。
合併reducer
通過上述的步驟咱們將一個大的reducer拆分紅了不一樣的小的reducer,但redux原則是隻容許一個根reducer,接下來咱們須要將這幾個小的reducer聚合到一個跟reducer中。
這裏咱們須要用到Redux 提供的combineReducers(reducers)
。
import {combineReducers} from 'redux'
import theme from './theme'
import sort from './sort'
import about from './about'
const index = combineReducers({
theme: theme,
sort: sort,
about: about,
})
export default index;
複製代碼
combineReducers()
所作的只是生成一個函數,這個函數來調用你的一系列 reducer,每一個 reducer 根據它們的 key 來篩選出 state 中的一部分數據並處理,而後這個生成的函數再將全部 reducer 的結果合併成一個大的對象。沒有任何魔法。正如其餘 reducers,若是 combineReducers()
中包含的全部 reducers 都沒有更改 state,那麼也就不會建立一個新的對象。
是存儲state的容器,Store 會把兩個參數(當前的 state 樹和 action)傳入 reducer。
store 有如下職責:
在前一個章節中,咱們使用 combineReducers()
將多個 reducer 合併成爲一個。如今咱們經過Redux的 createStore()
來建立一個Store。
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
複製代碼
咱們上文中所講的Action都是基於同步實現的,那麼對於網絡請求數據庫加載等應用場景同步Action顯然是不適用的,對此咱們須要用到異步Action。
咱們可將異步Action簡答理解爲:在Action中進行異步操做等操做返回後再dispatch一個action。
爲了使用異步action咱們須要引入
redux-thunk
庫,redux-thunk
是爲Redux提供異步action支持的中間件。
npm install --save redux-thunk
import thunk from 'redux-thunk'
let middlewares = [
thunk
]
//添加異步中間件redux-thunk
let createAppStore = applyMiddleware(...middlewares)(createStore)
複製代碼
export function onSearch(inputKey, token, popularKeys) {
return dispatch => {
dispatch({type: Types.SEARCH_REFRESH});
fetch(genFetchUrl(inputKey)).then(response => {//若是任務取消,則不作任何處理
return checkCancel(token) ? response.json() : null;
}).then(responseData => {
if (!checkCancel(token, true)) {//若是任務取消,則不作任何處理
return
}
if (!responseData || !responseData.items || responseData.items.length === 0) {
dispatch({type: Types.SEARCH_FAIL, message: inputKey + '什麼都沒找到'});
return
}
let items = responseData.items;
getFavoriteKeys(inputKey, dispatch, items, token, popularKeys);
}).catch(e => {
console.log(e);
dispatch({type: Types.SEARCH_FAIL, error: e});
})
}
}
複製代碼
默認狀況下,createStore() 所建立的 Redux store 沒有使用 middleware,因此只支持 同步數據流。
你可使用 applyMiddleware() 來加強 createStore()。它能夠幫助你用簡便的方式來描述異步的 action。
像 redux-thunk 或 redux-promise 這樣支持異步的 middleware 都包裝了 store 的 dispatch() 方法,以此來讓你 dispatch 一些除了 action 之外的其餘內容,例如:函數或者 Promise。你所使用的任何 middleware 均可以以本身的方式解析你 dispatch 的任何內容,並繼續傳遞 actions 給下一個 middleware。好比,支持 Promise 的 middleware 可以攔截 Promise,而後爲每一個 Promise 異步地 dispatch 一對 begin/end actions。
當 middleware 鏈中的最後一個 middleware 開始 dispatch action 時,這個 action 必須是一個普通對象;