博客地址:http://davidleee.com
原文鏈接:http://davidleee.com/2017/04/20/Build-App-With-Redux/
自從 Facebook 把 React Native 給開源了之后,使用前端技術進行移動應用開發的趨勢已經越來越明顯,現在連微軟爸爸都用上了 React Native(ReactXP),我們還有什么理由不來一點前端技術嘗嘗呢?
這篇文章是學習了公司現有項目框架、外加查閱一些官方文檔之后的總結,受限于我僅有的一丟丟前端知識,如果有錯誤或沒講清楚的地方,歡迎在評論里給我留言 :)
因為要把現有項目講清楚內容較多,所以分為兩個部分來總結。這篇是第一個部分,主要涉及 Redux 思想的簡單應用。
試想一下...
我們從一個實際可能會遇到的需求出發,考慮一下 Native 的實現和 React Native 的實現有什么不同。
來需求了
用戶在登錄之后,在首頁要顯示用戶的頭像和名字,然后還應該可以在某個地方看到自己的全部信息。
( oωo ) 那還不簡單!用戶登錄之后拉一下服務器上的用戶信息,找個合適的界面把信息一股腦顯示出來就好了,done!
簡單地改一下
用戶想要在我們的 App 上編輯自己的信息,我們簡單點來,直接在信息顯示的界面提供個編輯的入口就好了。
∑( ̄□ ̄;) ......嗯,這個要求也合情合理。
編輯之后發個消息通知其他地方,比如首頁的頭像和名字就可能要更新...某個列表似乎會根據用戶性別來決定顯示的內容,不過什么人會用著用著修改性別?!
來,這個更加簡單
果然修改一下用戶信息很簡單嘛,那再改回去也是一個道理吧,提供一個撤銷修改的按鈕吧。
Σ(°Д°; 這!
為什么用 Redux
如何優雅地實現上面的需求呢?如果我們的界面是與數據綁定的,并且數據的每一個狀態我們都可以追蹤到,那我們就不需要理會界面的更新,而可以專注于數據的修改了。
React Native 已經為我們提供了視圖與數據綁定的機制,那么狀態要怎么追蹤呢?
什么是 Redux
Redux 是一個為 JavaScript 應用設計的可預測的狀態容器。
簡單來說,Redux 使用了一種叫做 Action 的東西,每一次對狀態的變更(也就是對數據的變更)都需要通過 Action 來進行,然后通過另一個叫做 Reducer 的東西將 Action 和數據聯系起來。下面這張流程圖應該可以幫助你理解:
這張圖來自 Flux,Redux 可以被理解為是 Flux 的具體實踐。
顧名思義,Action 是一個操作,也可以理解為將要發生的事件,例如“修改用戶名”、“撤銷修改”;而 Reducer 則位于上圖 Dispatcher 和 Store 之間,負責將 Action 反應到對 Store 的修改上。
初衷
在上面的需求中,如果可以記錄下用戶的每一個操作和操作后的狀態,那么當用戶想要撤銷某項操作時,只需要將上一個操作給無效掉就可以了。更暴力一些,我們把應用啟動至今的所有用戶操作都重播一遍,唯獨不執行最后一次操作,就實現了“撤銷”的功能了吧?
Redux 提供的機制正好滿足了這個需求。
不止如此
React Native 實現的應用可以類比為使用 JavaScript 實現的單頁應用,隨著開發日趨復雜,我們的應用需要管理的狀態(state)會變得越來越多,管理的難度也將成倍增加。
Redux 提供的機制,通過限制數據更新發生的時間和方式,使得 state 的變化變得可以預測,后續的開發也可以放手施展了。
那該怎么做呢?
接下來我們分別看看 Redux 中主要的三部分是怎么實現的。
Action
為了描述一個動作,我們需要給這個動作一個名字和這個動作要操作的數據。舉個栗子,對于用戶想要改變用戶名的情況,我們可以這樣聲明這個動作:
// actions.js
export const CHANGE_USERNAME = 'CHANGE_USERNAME' // 1
// 2
export function changeUsername(username) {
return { type: CHANGE_USERNAME, username}
}
- 首先我們將這個動作稱為
CHANGE_USERNAME
,理論上每一個 Action 都應該有一個獨一無二的名字 - 通過一個創建函數,創建這個 Action 對象返回給調用方
例子里的動作名和創建函數都是 export
的,因為一般會把同一個模塊的動作聲明放到一起,方便外部的調用。
Reducer
需要注意的是,Reducer 必須是一個純函數。它不能修改傳入的參數并且不能有任何副作用,它必須要保證只要兩次傳入的參數相同,得出的結果也要相同。
先看代碼:
// reducers.js
import { CHANGE_USERNAME } from './actions'
// 1
const initialState = {
username: ""
}
function demoApp(state = initialState, action) { // 2
switch (action.type) { // 3
case CHANGE_USERNAME:
return { // 4
...state,
username: action.username
}
default:
return state // 5
}
}
export default demoApp
- 在 Redux 應用中,所有的
state
都被保存在一個單一的對象中 - 這里用到了 ES6 中的參數默認值語法,省去了初始化
state
的過程 - 當需要處理多個 Action 的時候,可以用
switch
、if/else
等方式區分不同的處理方式 - 再次強調,Reducer 必須是一個純函數,所以使用對象展開運算符把傳入的
state
展開,避免修改到里面的參數 - 還是因為純函數的原因,在不處理任何 Action 的時候,需要將傳入的
state
原封不動地再傳回去
通過上面的 Reducer,當有 CHANGE_USERNAME
這個動作發生時,應用會通過 demoApp
方法來更新 state
中的 username
參數,如果有視圖綁定在這個參數上,則界面也會發生相應的改變。
Store
有了 Action 和 Reducer,我們還需要將這二者聯系起來的途徑,稱為 Store。
Store 還具有維持應用 state
的職責,所以它在應用中也是單一存在的。當需要拆分數據處理邏輯的時候,應該做的是拆分 Reducer 而不是創建多個 Store。
繼續來看代碼:
// store.js
import { createStore } from 'redux'
import demoApp from './reducers'
let store = createStore(demoApp)
沒啦!我們需要用到的是 store
里面的方法,所以主要還是來看看怎么使用它們吧。
調用
直接來看代碼,我們假設已經存在一個用戶信息界面,用戶將在這里修改自己的名字:
// UserInfo.js
import { changeUsername } from './actions'
import { store } from './store'
store.dispatch(changeUsername("Lee"))
在這里,我們通過 changeUsername
方法構造了一個 Action,并告訴它我們要把名字改成什么;通過 store
中的 dispatch
方法,這個 Action 就會被發送 Reducer 中進行處理。
只是寫到這里,得益于純函數的實現,我們就已經可以給上述代碼中的所有方法寫單元測試了,就是這么簡單。
結語
上面的例子只涉及到了 Redux 最基本的使用方式,到目前為止還不能做出什么有用的東西來,不過用來讓我們感受 Redux 的主要思想應該是足夠了。
在下一篇文章中,才會講到 Redux 應用框架的搭建。其中會用到一些能有效減少代碼量的第三方框架(從上面的例子應該不難想象,當程序變復雜之后,這幾個類會變得多么龐大)。如果在學習了基礎知識之后還是看不懂一些現有項目的代碼的話,看完這些第三方框架的使用說不定就懂了呢!
更多更詳細的資料可以看參考資料中的網站。關于 React Native 的部分建議上官網看原版,我發現中文網的翻譯稍有缺失,更新也可能不那么及時。