#概述
隨著應用程式單頁面需求的越來越複雜,應用狀態的管理也變得越來越混亂,而Redux的就是為解決這一問題而出現的。在一個大型的應用程式中,應用的狀態不僅包括從伺服器獲取的資料,還包括本地建立的資料,以及反應本地UI狀態的資料,而Redux正是為解決這一複雜問題而存在的。
redux作為一種單向資料流的實現,配合react非常好用,尤其是在專案比較大,邏輯比較複雜的時候,單項資料流的思想能使資料的流向、變化都能得到清晰的控制,並且能很好的劃分業務邏輯和檢視邏輯。下圖是redux的基本運作的流程。
如上圖所示,該圖展示了Redux框架資料的基本工作流程。簡單來說,首先由view dispatch攔截action,然後執行對應reducer並更新到store中,最終views會根據store資料的改變執行介面的重新整理渲染操作。
同時,作為一款應用狀態管理框架,為了讓應用的狀態管理不再錯綜複雜,使用Redux時應遵循三大基本原則,否則應用程式很容易出現難以察覺的問題。這三大原則包括:
• 單一資料來源
整個應用的State被儲存在一個狀態樹中,且只存在於唯一的Store中。
• State是隻讀的
對於Redux來說,任何時候都不能直接修改state,唯一改變state的方法就是通過觸發action來間接的修改。而這一看似繁瑣的狀態修改方式實際上反映了Rudux狀態管理流程的核心思想,並因此保證了大型應用中狀態的有效管理。
• 應用狀態的改變通過純函式來完成
Redux使用純函式方式來執行狀態的修改,Action表明了修改狀態值的意圖,而真正執行狀態修改的則是Reducer。且Reducer必須是一個純函式,當Reducer接收到Action時,Action並不能直接修改State的值,而是通過建立一個新的狀態物件來返回修改的狀態。
redux的三大元素
和Flux框架不同,Redux框架主要由Action、Reducer和Store三大元素組成。
Action
Action是一個普通的JavaScript物件,其中的type屬性是必須的,用來表示Action的名稱,type一般被定義為普通的字串常量。為了方便管理,一般通過action creator來建立action,action creator是一個返回action的函式。
在Redux中,State的變化會導致View的變化,而State狀態的改變是通過接觸View來觸發具體的Action動作的,根據View觸發產生的Action動作的不同,就會產生不同的State結果。可以定義一個函式來生成不同的Action,這個函式就被稱為action creator。例如:
const ADD_TODO = `新增事件 TODO`;
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo(`Learn Redux`);
複製程式碼
上面程式碼中,addTodo就是一個action creator。但當應用程式的規模越來越大時,建議使用單獨的模組或檔案來存放action。
Reducer
當Store收到action以後,必須返回一個新的State才能觸發View的變化,State計算的過程即被稱為Reducer。Reducer本質上是一個函式,它接受Action和當前State作為引數,並返回一個新的State。格式如下:
const reducer = function (state, action) {
// ...
return new_state;
};
複製程式碼
為了保持reducer函式的純淨,請不要在reducer中執行如下的一些操作:
• 修改傳入引數;
• 執行有副作用的操作,如API請求和路由跳轉;
• 呼叫非純函式,如 Date.now() 或 Math.random()
對於Reducer來說,整個應用的初始狀態就可以直接作為State的預設值。例如:
const defaultState = 0;
const reducer = (state = defaultState, action) => {
switch (action.type) {
case `ADD`:
return state + action.payload;
default:
return state;
}
};
//手動呼叫
const state = reducer(1, {
type: `ADD`,
payload: 2
});
複製程式碼
不過,在實際使用過程中,reducer函式並不需要像上面那樣進行手動呼叫,Store的store.dispatch方法會觸發Reducer的自動執行。為此,只需要在生成Store的時候將Reducer傳入createStore方法即可。例如:
import { createStore } from `redux`;
const store = createStore(reducer);
複製程式碼
在上面的程式碼中,createStore函式接受Reducer作為引數,該函式返回一個新的Store,以後每當store.dispatch傳送過來一個新的Action,就會自動呼叫Reducer得到新的State。
##Store
Store就是資料儲存的地方,可以把它看成一個容器,整個應用中只能有一個Store。同時,Store還具有將Action和Reducers聯絡在一起的作用。Store具有以下的一些功能:
• 維持應用的 state;
• 提供getState()方法獲取state;
• 提供dispatch(action)方法更新state;
• 通過subscribe(listener)註冊監聽器;
• 通過subscribe(listener)返回的函式登出監聽器。
根據已有的Reducer來建立Store是一件非常容易的事情,例如Redux提供的createStore函式可以很方便的建立一個新的Store。
import { createStore } from `redux`
import todoApp from `./reducers`
// 使用createStore函式建立Store
let store = createStore(todoApp)
複製程式碼
其中,createStore函式的第二個引數是可選的,該引數用於設定state的初始狀態。而這對於開發同構應用時非常有用的,可以讓伺服器端redux應用的state與客戶端的state保持一致,並用於本地資料初始化。
let store = createStore(todoApp, window.STATE_FROM_SERVER)
複製程式碼
Store物件包含所有資料,如果想得到某個時刻的資料,則需要利用state來獲取。例如:
import { createStore } from `redux`;
const store = createStore(fn);
//利用store.getState()獲取state
const state = store.getState();
複製程式碼
Redux規定,一個state只能對應一個view,只要state相同得到的view就相同,這也是Redux框架的重要特性之一。
到此,關於Redux的運作流程就非常的清晰了,下面總結下Redux的運作流程。
- 當使用者觸控介面時,呼叫store.dispatch(action)捕捉具體的action動作。
- 然後Redux的store自動呼叫reducer函式,store傳遞兩個引數給reducer函式:當前state和收到的action。其中,reducer函式必須是一個純函式,該函式會返回一個新的state。
- 根reducer會把多個子reducer的返回結果合併成最終的應用狀態,在這一過程中,可以使用Redux提供的combineReducers方法。使用combineReducers方法時,action會傳遞給每個子的reducer進行處理,在子reducer處理後會將結果返回給根reducer合併成最終的應用狀態。
- store呼叫store.subscribe(listener)監聽state的變化,state一旦發生改變就會觸發store的更新,最終view會根據store資料的更新重新整理介面。
Redux實現
1,建立store
store就是redux的一個資料中心,簡單的理解就是我們所有的資料都會存放在裡面,然後在介面上使用時,從中取出對應的資料。因此首先我們要建立一個這樣的store,可以通過redux提供的createStore方法來建立。
xport default function createStore(reducer, preloadedState, enhancer) {
...
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
複製程式碼
可以看到createStore有三個引數,返回一個物件,裡面有我們常用的方法,下面一一來看一下。
getState
getState用於獲取當前的狀態,格式如下:
function getState() {
return currentState
}
複製程式碼
Redux內部通過currentState變數儲存當前store,變數初始值即我們呼叫時傳進來的preloadedState,getState()就是返回這個變數。
subscribe
程式碼本身也不難,就是通過nextListeners陣列儲存所有的回撥函式,外部呼叫subscribe時,會將傳入的listener插入到nextListeners陣列中,並返回unsubscribe函式,通過此函式可以刪除nextListeners中對應的回撥。以下是該函式的具體實現:
var currentListeners = []
var nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice() //生成一個新的陣列
}
}
function subscribe(listener) {
if (typeof listener !== `function`) {
throw new Error(`Expected listener to be a function.`)
}
var isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
var index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
複製程式碼
可以發現,上面的原始碼使用currentListeners和nextListeners兩個陣列來儲存,主要原因是在dispatch函式中會遍歷nextListeners,這時候可能會客戶可能會繼續呼叫subscribe插入listener,為了保證遍歷時nextListeners不變化,需要一個臨時的陣列儲存。
dispatch
當view dispatch一個action後,就會呼叫此action對應的reducer,下面是它的原始碼:
function dispatch(action) {
...
try {
isDispatching = true
currentState = currentReducer(currentState, action) //呼叫reducer處理
} finally {
isDispatching = false
}
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i]
listener()
}
...
}
複製程式碼
從上面的原始碼可以發現,dispatch函式在呼叫了currentReducer以後,遍歷nextListeners陣列,回撥所有通過subscribe註冊的函式,這樣在每次store資料更新,元件就能立即獲取到最新的資料。
replaceReducer
replaceReducer是切換當前的reducer,雖然程式碼只有幾行,但是在用到時功能非常強大,它能夠實現程式碼熱更新的功能,即在程式碼中根據不同的情況,對同一action呼叫不同的reducer,從而得到不同的資料。
function replaceReducer(nextReducer) {
if (typeof nextReducer !== `function`) {
throw new Error(`Expected the nextReducer to be a function.`)
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
複製程式碼
bindActionCreators
bindActionCreators方法的目的就是簡化action的分發,我們在觸發一個action時,最基本的呼叫是dispatch(action(param))。這樣需要在每個呼叫的地方都寫dispatch,非常麻煩。bindActionCreators就是將action封裝了一層,返回一個封裝過的物件,此後我們要出發action時直接呼叫action(param)就可以了。
react-redux
redux作為一個通用的狀態管理庫,它不只針對react,還可以作用於其它的像vue等。因此react要想完美的應用redux,還需要封裝一層,react-redux就是此作用。react-redux庫相對簡單些,它提供了一個react元件Provider和一個方法connect。下面是react-redux最簡單的寫法:
import { Provider } from `react-redux`; // 引入 react-redux
……
render(
<Provider store={store}>
<Sample />
</Provider>,
document.getElementById(`app`),
);
複製程式碼
connect方法複雜點,它返回一個函式,此函式的功能是建立一個connect元件包在WrappedComponent元件外面,connect元件複製了WrappedComponent元件的所有屬性,並通過redux的subscribe方法註冊監聽,當store資料變化後,connect就會更新state,然後通過mapStateToProps方法選取需要的state,如果此部分state更新了,connect的render方法就會返回新的元件。
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
...
return function wrapWithConnect(WrappedComponent) {
...
}
}
複製程式碼
本文不詳細介紹React-Redux,可以訪問下面的連結React-Redux簡介及應用。