1.使用場景
redux雖然好,也并不是什么情況下都要使用,如果在項目中遇到一下場景,你會自發(fā)的尋找一個工具來解決問題,這時就是redux隆重登場的時候了:
業(yè)務層面:
用戶的使用方式復雜
不同身份的用戶有不同的使用方式(比如普通用戶和管理員)
多個用戶之間可以協(xié)作
與服務器大量交互,或者使用了WebSocket
View要從多個來源獲取數(shù)據(jù)
組件層面:
某個組件的狀態(tài),需要共享
某個狀態(tài)需要在任何地方都可以拿到
一個組件需要改變?nèi)譅顟B(tài)
一個組件需要改變另一個組件的狀態(tài)
如果通過上面的考慮,你還是不知道是否需要使用Rduex,那很有可能就是你不需要使用
2.設計思想
兩句話概括:
1. Web 應用是一個狀態(tài)機,視圖與狀態(tài)是一一對應的。
2. 所有的狀態(tài),保存在一個對象里面。

結合這個圖對原理做一下解釋,也許有些名字還是不懂,但是可以先有個印象,接下來我們會做進一步的介紹。
用戶會在view上進行一系列操作,而每一個操作都對應一個action。Store對象通過調用dispatch方法,傳入上次的狀態(tài)(previousState)和操作(action),經(jīng)過Reducers針對不同的action,改變previousState的值,然后返回一個newState,然后Stroe對象可以通過getState()方法,獲取這個新狀態(tài)。最后進行相應的視圖更新。web應用所有的狀態(tài)都保存在state中,每一個state都對應一個視圖。
3. 核心概念
state:
整個Redux都是圍繞狀態(tài)進行的操作,所有的 state 都被保存在一個單一對象中。建議在寫代碼前先想一下這個對象的結構。如何才能以最簡的形式把應用的 state 用對象描述出來。如果應用較復雜在創(chuàng)建store對象時,可以這些意見:http://cn.redux.js.org/docs/recipes/reducers/NormalizingStateShape.html。應用的初始狀態(tài)可以在創(chuàng)建Store時傳入,也可以根據(jù)reducer中state的默認值自動生成。
action:
每一個action都有個對應的操作,所以action中有這個操作的名字(類型)和需要的數(shù)據(jù)。action 是把數(shù)據(jù)從應用傳到 store 的有效載荷,它是Store數(shù)據(jù)的唯一來源。一般來說你會通過 store. dispatch() 將 action 傳到 store;它本質上是JavaScripte的普通對象:
{
type: “ADD_TODO”,
text: 'Build my first Redux app'
}
對象中type字段一般是必須的,用來描述操作的類型。其他的參數(shù)都可以根據(jù)操作的需要,進行自定義擴充。
這個例子action是像toDolist中添加一項,text字段是這個todo的內(nèi)容。很明顯我們不能每一個不同的內(nèi)容都有個action,這個需要action creator:
let todo = function (text) {
return {
type: "ADD_TODO",
text
}
}
reducer:
action只是描述了一個操作,而沒有描述如何針對這個action進行狀態(tài)更改。reducer則描述了如何根據(jù)傳入的action,對狀態(tài)做相應的改變。
在編寫reducer函數(shù)是要保證它的純凈性,不要進行以下操作:
不要修改傳入的參數(shù),尤其是在處理state時,要返回新的對象
執(zhí)行有副作用的操作,如 API 請求和路由跳轉
調用非純函數(shù),如 Date.now() 或 Math.random()
要保證只要傳入?yún)?shù)相同,返回計算得到的下一個 state 就一定相同。沒有特殊情況、沒有副作用,沒有 API 請求、沒有變量修改,單純執(zhí)行計算
Store:
Store 就是把state、action、 reducers聯(lián)系到一起的對象。Store 有以下職責
維持應用的 state
提供 getState() 方法獲取 state
提供 dispatch(action) 方法,然后通過reducer更新 state
通過 subscribe(listener) 注冊監(jiān)聽器
通過subscribe(listener) 返回的函數(shù)注銷監(jiān)聽器
Redux應用只有一個單一的 store。當需要拆分數(shù)據(jù)處理邏輯時,你應該使用 reducer 組合而不是創(chuàng)建多個 store。
// createStore的簡單實現(xiàn)
const createStore = (reducer) => { // 源碼支持傳入三個參數(shù),reducer, preloadedState, enhancer
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
}
};
dispatch({}); //獲得初始狀態(tài)
return { getState, dispatch, subscribe };
};
數(shù)據(jù)流:
嚴格的單向數(shù)據(jù)流是 Redux 架構的設計核心:
用戶行為引起調用store.dispatch(actioin)
調用reduser函數(shù):兩個參數(shù),當前的state和action
返回計算過的state
觸發(fā)所有訂閱store.subscribe(listener)
4. 簡單實例
下面是一個toDoList的簡單實例
http://jsbin.com/noxalaw/edit?html,js,console,output
5.高級用法
異步action:
上面講到的action觸發(fā)的動作都是同步的,如果有異步的action該如何處理呢?同步時只需觸發(fā)一個action,當異步時要要觸發(fā)三個action,分別為:
發(fā)起:操作開始時,送出一個 Action,觸發(fā) State 更新為"正在操作"狀態(tài),View 重新渲染
成功: 操作結束后,再送出一個 Action,觸發(fā) State 更新為"操作結束"狀態(tài),View 再一次重新渲染
失?。?請求失敗時發(fā)出action,觸發(fā)state更新,view顯示錯誤狀態(tài)
這三個action對應的描述如下:
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
給出一個異步action的示例:
const fetchPosts = subreddit => dispatch => {
dispatch(requestPosts(subreddit)) //開始action
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(subreddit, json))) //成功action
}
Actions creator現(xiàn)在返回了一個函數(shù),而在同步時返回一個普通對像。在默認情況下dispatch的參數(shù)只能是一個對象,不能是一個函數(shù)。這個時候就需要引入中間件: redux-thunk,讓dispatch參數(shù)可以傳入函數(shù)。
所以,異步操作的一種解決方案就是,
一個返回函數(shù)的 Action Creator,
使用redux-thunk中間件改造store.dispatch,使其能支持函數(shù)作為參數(shù)
至于中間件是如何操作的,這里就不細述了,有興趣的同學可以參考http://cn.redux.js.org/docs/advanced/Middleware.html
// redux-thunk的源碼
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
6. 異步action的完整示例
http://jsbin.com/xexax/edit?html,js,console,output
參考:
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html