玩轉redux--從會用到庖丁解牛
目錄
為何而寫
以前沒怎麼寫過技術類的文章,對於技術很多時候是現用現學,這樣通常能解決遇到過的絕大多數問題,但也帶來了一個弊端,那就是僅停留於解決問題的層面,而忽略了對技術背後的設計思想和設計理念進行深入的研究。雖然技術的發展日新月異,但那些終歸只是由設計思想演繹出來的玩物,如果只是不斷的去學習演繹出來的東西,而忽略了對思想的研究,就終究會陷入舍本逐末的惡性迴圈。
技術的提高離不開實踐,實踐有助於深化對技術的理解,但如果只實踐而不加以總結的話也容易陷入只見樹木不見森林的迷途。如果說實踐是戰術上的肉搏,那總結就是戰略上的提煉,高屋建瓴,將實踐中的精髓抽取出來,以達到去繁入簡、返璞歸真的境界。
鑑於此,也就打算把學習、實踐過的技術進行再次的學習、總結,並以博文的形式記錄下來,這樣一方面有助於理清思路,建立知識體系,另一方面更有助於深入對技術的理解和掌握,這也是我打算開始寫技術文章的初衷。
redux是什麼
redux是一個庫,用於管理狀態,也可以說是一個容器,這個容器裡容納了各種需要的狀態資訊,並對外提供了一些方法來管理這些狀態。目前redux更多的實踐是和react結合,管理react的檢視狀態,但它也可以獨立當作釋出、訂閱系統來使用。總之這些只是概念層面上的東西,實踐完之後再來看概念就會容易理解,否則概念永遠是模糊的。
redux的設計哲學
redux有三大設計哲學:
1、單一資料來源。
顧名思義就是所有的狀態資訊都儲存在同一個容器裡。
2、狀態是隻讀的。
redux是用來管理狀態的,竟然能管理,那就包含了對狀態的增、刪、改操作。這裡的只讀是指新狀態不會破壞原來的舊狀態,也就是說新狀態的產生過程是:1、先從舊狀態進行深拷貝得到一個複本。2、對複本進行操作得到新狀態。
3、使用純函式來更新狀態。
純函式是指輸入相同的引數時,總能得到相同的輸出結果,並且不會修改輸入的引數。
這三大設計哲學主要是為了簡化對狀態的管理,讓狀態可預測、可追蹤,從而易於維護程式碼,也更易於排查bug。
redux的工作流
redux的工作流如下圖所示:
https://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#/9
redux的幾個核心要素
store
redux是管理狀態的,所有的狀態都儲存在“store”裡,所以store是一個數據中心,也是最核心的要素。同時store對外暴露了一些api用於管理store裡的資料,這些api包括getState、dispatch、subscribe、replaceReducer。store是由createStore函式建立的,如:
const store = createStore(reducer, preloadedState, enhancer)
createStore的實現原理如下:
const createStore = reducer => { let state; // listeners用來儲存所有的監聽函式 let listeners = []; const getState = () => state; const dispatch = action => { state = reducer(state, action); // 每一次狀態更新後,都需要呼叫listeners陣列中的每一個監聽函式 listeners.forEach(listener => listener()); } const subscribe = listener => { // subscribe可能會被呼叫多交,每一次呼叫時,都將相關的監聽函式存入listeners陣列中 listeners.push(listener); // 返回一個函式,進行取消訂閱 return () => { listeners = listeners.filter(item => item !== listener); } } return { getState, dispatch, subscribe }; }
dispatch
dispatch用來派發一個action,所有對state的更新以及獲取資料都是通過派發一個action來完成的。如:
let action1 = { type: 'READ_BOOK', data: { book: 'book1' } }; store.dispatch(action1);
getState
getState用於獲取store裡的狀態資料,即state。如:
let state = store.getState();
subscribe
subscribe用於訂閱store裡的資料狀態,當store裡state資料變更時,通知訂閱方。如:
const render = () => { ... } store.subscribe(render);
replaceReducer
這個一般開發用不到。
action
action是指一個操作動作,當需要操作store裡的資料時,就需要分發一個action來通知store。每個action描述了一個操作,它是一個object的資料結構,有一個必填屬性“type”,值是字串常量,用於標識動作,可以理解為動作的ID,另外action物件裡還可以攜帶其他資料資訊,這要根據action所要完成的操作來定。
reducer
reducer是用來響應action來處理狀態的,它會對每個分發的action進行操作,根據傳過來的action和store裡已有的sate值進行一些運算,然後返回新的state。
actionCreator
actionCreator是一個類似於工廠模式的生產工具,用於生產action。如:
const learnReduxActionFactory = book => { type: 'READ_REDUX_BOOK', book }
combineReducers
combineReducers是Redux提供的一個工具函式,可以把多個拆分的reducer合併成一完整的reducer。
例如在頁面狀態中儲存三種資料狀態,分別為data1、data2和data3, 它們是相互獨立的。如:
state = { data1: { ... }, data2: { ... }, data3: { ... } };
同樣我們把處理這三種資料狀態的reducer函式也拆分成三個小的reducer,分別為reducer1、reducer2和reducer3。如下:
const reducer1 = (previousState= {}, action) => { // 根據action和state.data1計算產生新的state.data1 return state.data1; }; const reducer2 = (previousState= {}, action) => { // 根據action和state.data2計算產生新的state.data2 return state.data2; } const reducer3 = (previousState= {}, action) => { // 根據action和state.data3計算產生新的state.data3 return state.data3; }
最後利用combineReducer將這三個子reducer函式合併成一個完整的reducer並返回。如下:
const { combineReducers } = Redux; const finalReducer = combineReducers({ data1: reducer1, data2: reducer2, data3: reducer3 });
在ES6開發環境下,常用的做法是令子reducer函式名稱和資料狀態的命名保持一致,即將reducer1、reducer2、reducer3分別命名為data1、data2、data3。即:
const finalReducer = combineReducers({ data1: data1, data2: data2, data3: data3 });
也可以簡寫為:
const finalReducer = combineReducers({data1, data2, data3});
日常開發中,通常會將大的reducer拆分成多個小的reducer進行單獨維護,這樣的分治能提高維護效率。
redux中介軟體
redux中介軟體提供的是位於action被派發之後,到達reducer之前的擴充套件點,因此可以利用redux中介軟體來完成日誌記錄、呼叫非同步介面或者路由等。redux中介軟體的作用主要有兩個:
1、截獲action
2、發出action
redux中介軟體非同步請求的工作流如下圖所示:
https://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#/23
小結
redux既可以作為釋出訂閱系統獨立使用,也可以和react結合來管理react元件的狀態。目前最常見的使用場景是與react結合,讓react元件間通訊更加容易。另外通過使用豐富的redux中介軟體,能擴充套件系統的特性。