從上周開始一直在看 Redux 相關(guān)的文檔和資料姑子,一開始一連幾天都沒有進(jìn)展,只能一邊看文檔一邊默默 debug测僵,到今天總算稍微理解了一點(diǎn)兒街佑,于是趕緊開始寫這篇博客,目的是一點(diǎn)點(diǎn)記錄下自己入門 Redux 的過程以及作為一個(gè) React Native 菜鳥對(duì)使用 Redux 的心得體會(huì)捍靠。
(長文預(yù)警:本文最佳閱讀方式是先把 Redux 文檔 以及 React Redux API 文檔 在另一個(gè)瀏覽器中打開沐旨,隨時(shí)對(duì)比參考,然后把 Demo 項(xiàng)目打開榨婆,看到有疑惑的地方磁携,隨時(shí)停下來動(dòng)手寫一寫、改一改代碼良风,debug 下谊迄,然后再接著看)
00 What's Redux?
首先看下官方文檔的解釋『Redux is a predictable state container for JavaScript apps』,大致可以翻譯為 Redux 是一個(gè)專為 JavaScript 應(yīng)用設(shè)計(jì)的可預(yù)測的狀態(tài)容器烟央。
我的理解:如果把我們的 app 比做銷售柜臺(tái)的話统诺,Redux 其實(shí)就像一個(gè)自動(dòng)售貨機(jī),它可以幫助我們管理商品(組件)的各種狀態(tài)吊档。
我們知道在一個(gè) RN 項(xiàng)目中篙议,一般會(huì)定義許許多多的組件,每個(gè)組件都有自己的 State
怠硼,通常情況下會(huì)通過 setState
去更改組件狀態(tài)或者響應(yīng)用戶的在 UI 上的輸入鬼贱,但是隨著一個(gè) App 的內(nèi)容的增加以及邏輯復(fù)雜度的上升,組件的的狀態(tài)會(huì)變得越來越臃腫香璃,繼而變得難以維護(hù)(維護(hù)困難也是 RN 最大的痛點(diǎn))这难。所以,這個(gè)時(shí)候就該 Redux 出場了葡秒,我們通過引入它來幫助我們管理組件的狀態(tài)姻乓。
簡單來說嵌溢,有了 Redux 之后,我們基本上就不需要自己去 setState
了蹋岩,因?yàn)閹缀跛械?State 的變化都可以交給 Redux 來管理赖草。
01 Core Concept
Redux 中有四個(gè)核心的概念是需要我們掌握的,分別是 Actions
, Reducers
, Store
以及 Data Flow
(強(qiáng)烈推薦看一遍 Redux 文檔 里對(duì)這幾個(gè)概念的介紹)剪个。
看過文檔后差不多就可以知道秧骑,我們通過在需要改變 state 的地方發(fā)送(dispatch) action,然后在 reducer 中根據(jù) action 的 type 進(jìn)行處理(返回增刪改后的 state)扣囊,而 state 則是根據(jù) reducer 的組合結(jié)構(gòu)保存在 store 的 state 樹中的乎折。
文字太枯燥,來看下面這張圖輔助理解:
我簡單分析下這張圖吧:
- 首先 React 組件綁定了 Redux 之后(主要借助 React-Redux侵歇,下面會(huì)說到)骂澄,就可以使用
store.dispatch()
去發(fā)送事件了,比如在按鈕的點(diǎn)擊事件中發(fā)送 action惕虑; - store 接收到 action 之后坟冲,會(huì)把之前的 state 連同發(fā)送的 action 一起傳遞給 reducer,然后 reducer 會(huì)根據(jù)我們定義好的處理方式去處理 action (增刪改 state)枷遂,然后返回新的 state 讓 store 去更新全局 state 樹樱衷;
- 最終 store 會(huì)把新的 state 返回給發(fā)送 action 的地方好讓 React 組件更新 UI。
Q & A
這里再總結(jié)一些 Redux 新手會(huì)比較疑惑的點(diǎn):
問:什么是 Action Creator
?
答:顧名思義酒唉,Action Creator
就是生成 action 的函數(shù),我們知道 action 是一個(gè)對(duì)象沸移,其中攜帶了一些用于更新 state 的數(shù)據(jù)痪伦。想象一下,如果我們每次發(fā)送事件的時(shí)候都自己定義一個(gè) action 對(duì)象雹锣,那么無疑是非常容易出錯(cuò)的网沾。而使用 Action Creator
后,管理 action 就容易多了蕊爵,而且也不用擔(dān)心在 action 中用錯(cuò)屬性了辉哥,因?yàn)橹灰?Action Creator
中定義好參數(shù)后,我們就可以在發(fā)送該 action 的地方確定需要攜帶哪些數(shù)據(jù)了攒射。比如:
export function changeText(text, color) {
return {
type: CHANGE_TEXT,
text,
color,
};
}
問:reducer 當(dāng)中是如何更改 state 的醋旦?可以在 reducer 中發(fā)請求嗎?
答:不可以会放!注意饲齐,reducer 只是通過接收 action 去處理 state 的變化,我們不可以在 reducer 中引入變化咧最。引用文檔上的話:
Remember that the reducer must be pure. Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.
也就是說 reducer 必須是純函數(shù)捂人,接收輸入御雕,返回輸出,只是純粹的計(jì)算滥搭。舉個(gè)例子:
function textReducer(state = {}, action) {
let text = action.text;
let color = action.color;
// 判斷 action 類型
switch (action.type) {
case CHANGE_TEXT:
// 返回新狀態(tài)
return Object.assign({}, state, {
text: '新文字:' + text,
color: color
});
default:
return state;
}
}
需要注意的是酸纲,我們不可以改變原來的 state,也就是要保持 Object.assign()
中的第一個(gè)參數(shù)為空瑟匆,當(dāng)然也可以使用擴(kuò)展運(yùn)算符 {...state, {newState}}
來達(dá)到同樣的目的福青。
問:React 組件是如何更新的?
答:哈哈脓诡,這個(gè)其實(shí)就是真正體現(xiàn)出 Redux 優(yōu)勢的地方无午。之前說過有了 Redux 之后我們就不需要自己去管理 state,這是怎么做到的呢祝谚?其實(shí)當(dāng)我們把所有的 state 都交給 Redux 之后宪迟,Redux 在 store 中就保存了我們應(yīng)用所有的 state (state tree),這意味著我們可以在任意位置獲取到這些 state交惯。而在 RN 中次泽,我們通過 React-Redux 來連接 Redux 和 RN 組件,然后在組件中通過將 state 映射成 props 來使用席爽,而且我們還可以將 dispatch 也映射成 props 來使用意荤。也就是說,我們只需要使用 this.props.xxx
就可以完成數(shù)據(jù)的展示只锻、響應(yīng)用戶輸入以及 UI 變化了玖像,是不是非常神奇?后面 03 段會(huì)詳細(xì)說我們是怎么通過 React-Redux 來做到這一點(diǎn)的齐饮。
02 Basic Usage
了解了 Redux 基本概念之后就可以來學(xué)習(xí)一下如何使用了捐寥。這一節(jié)主要介紹 API 的使用。
Store
之前舉了個(gè) Action Creator 和 reducer 的例子祖驱,也知道了我們需要通過 store 來將這兩者綁定在一起握恳,但是 store 究竟還有哪些作用呢?store 主要有以下職責(zé):
- 保存應(yīng)用狀態(tài)(state)
- 提供對(duì) state 的訪問:getState()
- 提供對(duì)狀態(tài)進(jìn)行更新的方法: dispatch(action)
- 注冊監(jiān)聽器: subscribe(listener)
- 通過 subscribe(listener) 返回的方法處理解注冊監(jiān)聽器
可以看出 store 就像母體一樣捺僻,即保存了狀態(tài)又連接了 Action Creator 和 reducer乡洼。但是究竟是怎么連接的呢?
首先匕坯,之前說過我們的 reducer 可以定義 store 保存的 state 樹結(jié)構(gòu)束昵,為什么呢?因?yàn)?reducer 是可以層層組合的醒颖。通常一個(gè)組件會(huì)有很多層級(jí)以及類型(比如 UI 和數(shù)據(jù))的 state妻怎,我們不可能全都把他們定義在一個(gè) reducer 里,這樣代碼會(huì)變得又長又難以維護(hù)泞歉。正確的做法是我們應(yīng)該把一個(gè) reducer 拆分成多個(gè) reducer 后再通過某種方式將它們組合起來逼侦。
combineReducers(reducers)
最常見的就是使用 combineReducers() 方法來組合 reducer匿辩,它可以把多個(gè) reducer 合并成一個(gè) reducer 對(duì)象,而合并后的 reducer 的層級(jí)結(jié)構(gòu)就是我們訪問 state 時(shí)的層級(jí)結(jié)構(gòu)榛丢。接收的參數(shù)是一個(gè) reducer 對(duì)象铲球,我們可以自己指定 key,也可以直接組合晰赞,比如:
const allReducers = combineReducers({textReducer, imageReducer, videoRed: videoReducer});
createStore(reducer, [preloadedState], [enhancer])
當(dāng)把多個(gè) reducer 合并成一個(gè)之后稼病,我們就可以通過 createStore 來創(chuàng)建 store 了,它唯一必須的參數(shù)是 reducer掖鱼,也就是我們 combineReducer(reducers)
返回的 reducer然走。
第二個(gè)參數(shù)是可選的初始狀態(tài) preloadedState
。我們可以從服務(wù)器讀取初始狀態(tài)戏挡,或者在 app 啟動(dòng)時(shí)從本地?cái)?shù)據(jù)讀取初始狀態(tài)芍瑞。如果使用了 combineReducer()
后的 reducer 作為第一個(gè)參數(shù),那么 preloadedState
就必須是結(jié)構(gòu)和 reducer 中的結(jié)構(gòu)保持一致褐墅。
第三個(gè)參數(shù)也是可選的 enhancer
拆檬,接收的是一個(gè)函數(shù),可以是你想要提供的第三方的功能妥凳,比如 Redux 自帶的 applyMiddleware()竟贯,這個(gè)屬于高階的內(nèi)容,以后有時(shí)間會(huì)寫寫逝钥。舉個(gè)例子:
const allReducers = combineReducers({textReducer});
let store = createStore(allReducers, applyMiddleware(thunkMiddleware));
export default store;
createStore()
返回的 store 就是保存了應(yīng)用全局 state 的對(duì)象了屑那,這個(gè)時(shí)候我們就可以使用 getState()
并且根據(jù) reducer 的層級(jí)來訪問保存在其中的 state 了!
Others...
其實(shí)核心的 API 就是以上這些晌缘,但是下面這些也是比較常見的齐莲,這里貼個(gè)鏈接自己看吧(???)
03 Combine with React-Redux
理解了 Redux 的核心概念以及基本 API 之后,我們就可以開始嘗試學(xué)習(xí)如何在 RN 中使用 Redux 了磷箕。在 RN 中,我們一般會(huì)結(jié)合 React-Redux 來使用 Redux阵难,通過它我們可以很方便地把 Redux 和 RN 組件綁定在一起岳枷。React-Redux 也是 Redux 官方推薦的用來和 React 綁定 的框架,其實(shí)我也有點(diǎn)奇怪為什么不直接把 React-Redux 加入到 Redux 中去呜叫?(?_?ヾ
同樣的空繁,在介紹 React-Redux 具體用法之前,建議先把 API 文檔 打開放邊上供隨時(shí)參考朱庆。
<Provider>
從文檔上可以發(fā)現(xiàn)盛泡,React-Redux 的 API 分為兩部分,分別是 <Provider store>
以及connect()
方法娱颊。connect()
方法用于連接單個(gè)的 RN 組件和 Redux Store
傲诵,而 Provider
則作為根布局用于包裝所有的 RN 組件凯砍,使得其中使用了 connect()
后的組件可以自由訪問 Redux Store
。
比如假設(shè)你的根部組件是 <Main/>
(如果你使用 react-native-router-flux 作為 navigation 的話拴竹,那么根布局就是 <Router>
)悟衩,用 <Provider>
包裝后就是這樣的:
<Provider store={store}>
<Main />
</Provider>
而在你的容器(Container,下面會(huì)說到)中栓拜,需要使用 connect()
方法來連接 RN 組件和 store座泳,像這樣:
export default connect(mapStateToProps, {changeText, changeBack})(Main);
connect()
React-Redux 的核心就是 connect()
方法了,所以下面詳細(xì)介紹下 connect()
方法的各個(gè)參數(shù)幕与。
調(diào)用 connect()
方法不會(huì)修改組件挑势,但是會(huì)返回一個(gè)已經(jīng)和 store 建立連接后的新組件,它使用了裝飾器模式啦鸣,裝飾的是 connectAdcanced()
潮饱,這個(gè)方法返回一個(gè)接收需要連接的組件的方法(比如上面的 Main
)。
它的參數(shù)如下(建議參照文檔看赏陵,我的翻譯也許有誤):
mapStateToProps(state, [ownProps])
這是一個(gè) function
類型的參數(shù)饼齿。
如果指定了這個(gè)參數(shù),那么返回的新組件就會(huì)接收到 store 的更新蝙搔。也就是說每次 store 發(fā)生更新時(shí)這個(gè)方法都會(huì)被調(diào)用缕溉。使用方式如下:
參數(shù):
-
state
: 即 state 發(fā)生更新后接收到的新 state -
ownProps
: 可選,新組件接收到的 props吃型,如果指定了該值证鸥,那么這個(gè)方法也會(huì)被調(diào)用
返回值:
- 必須是對(duì)象
object
,并且該對(duì)象會(huì)被合并到組件的 props 中去勤晚。我們一般將接收到的 state 里面關(guān)于該組件有用到的部分取出枉层,映射成對(duì)應(yīng)的 props 就可以了,其實(shí)這個(gè)方法就是一個(gè)選擇器(selector)
Tips: 如果不想接收到 state 的更新赐写,可以傳 null
或 undifined
mapDispatchToProps(dispatch, [ownProps]) or Action Creators
這是一個(gè) function
或 object
類型的參數(shù)鸟蜡。
如果指定的是 object
,那么其中的每個(gè)屬性必須是都是 Action Creator挺邀,每個(gè) Action Creator 都會(huì)被包裝到 dispatch()
中揉忘,并且合并到組件的 props 中去。
如果指定的是 function
端铛,使用如下:
參數(shù):
-
dispatch
: store 的dispatch()
方法泣矛,你可以自己決定如何使用dispatch
去發(fā)送事件(當(dāng)然也是通過 Action Creator),比如使用 bindActionCreators() -
ownProps
: 可選禾蚕,和mapStateToProps
一樣也是新組件接收到的 props
返回值:
- 同樣是一個(gè)對(duì)象
object
您朽,會(huì)被合并到 props 中作為發(fā)送事件的方法來使用
Tips: 如果不指定該參數(shù)(mapDispatchToProps or Action Creator)那么 React- Redux 默認(rèn)會(huì)把 dispatch
方法注入到組件中,方便你自己手動(dòng)去調(diào)用 Action Creator换淆。
mergeProps(stateProps, dispatchProps, ownProps)
這是一個(gè) function
類型的參數(shù)哗总。
該方法用于將前面兩個(gè)參數(shù)返回的對(duì)象以及組件自己的 props 進(jìn)行重新組合或者過濾几颜,一般如果 props 較多的話,我們可以指定這個(gè)方法來對(duì) props 進(jìn)行篩選魂奥,然后再合并到組件中菠剩。使用方法如下:
參數(shù):
stateProps
: 表示mapStateToProps()
返回的對(duì)象dispatchProps
: 表示mapDispatchToProps()
返回的對(duì)象ownProps
: 組件自己的 props
返回值:
- 篩選后的對(duì)象,將會(huì)被合并到組件的 props 中去
Tips: 如果不指定該方法耻煤,默認(rèn)將調(diào)用 Object.assign({}, ownProps, stateProps, dispatchProps)
來合并所有的 props具壮。
options
這是一個(gè) object
類型的參數(shù)。
該方法用于定制連接器的行為哈蝇,具體可使用的參數(shù)有:
[
pure
] (Boolean): 如果為 true 則當(dāng) state/props 值沒有發(fā)生變化的時(shí)候則不重新渲染(rerender)或者調(diào)用mapStateToProps()
ormapDispatchToProps()
orownProps
棺妓,前提是組件必須是"純"的(PureComponent?)炮赦,不依賴組件內(nèi)部的 state 而只依賴 props 和 Redux store怜跑。[
areStatesEqual
] (Function): 當(dāng)是純組件時(shí),比較接收到的新 state 和之前的值吠勘,默認(rèn)值strictEqual (===)
[
areOwnPropsEqual
] (Function): 當(dāng)是純組件時(shí)性芬,比較接收到的新 props 和之前的值,默認(rèn)值 shallowEqual[
areStatePropsEqual
] (Function): 當(dāng)是純組件時(shí)剧防,比較mapStateToProps()
接收到的新 state 和之前的值植锉,默認(rèn)值 shallowEqual[
areMergedPropsEqual
] (Function): 當(dāng)是純組件時(shí),比較mergeProps()
接收到的新 props 和之前的值峭拘,默認(rèn)值 shallowEqual[
storeKey
] (String): 從當(dāng)前上下文中讀取 store 的 key俊庇,當(dāng)你有多個(gè) store 的時(shí)候才會(huì)使用這個(gè)參數(shù),不推薦使用多個(gè) store鸡挠,默認(rèn)值store
Others
當(dāng)然辉饱,以上是核心 API,下面兩個(gè)方法不是很常用拣展,所以不多介紹了彭沼,自己看去吧~′???`
04 Show Me The Code
能看到這里說明你真的很好學(xué)啊,既然這樣那就一起來擼一下代碼吧(??????)备埃。Demo 地址:aJIEw/Redux
首先說明下溜腐,以下的項(xiàng)目分包是推薦的做法,你不一定需要完全遵守:
- components [folder]
- containers [folder]
- redux [folder]
- actions [folder]
- reducers [folder]
- store.js [file]
Components & Containers
Components 和 Containers 下面放的是兩種不同的組件瓜喇。這里的 Component 和 React 中的 Component
是有區(qū)別的,專指那些只是用來展示數(shù)據(jù)的歉糜、無狀態(tài)的純組件乘寒。Components 的特點(diǎn)是:1)通過 props 來接收數(shù)據(jù);2)可在多處復(fù)用匪补。由于是單純用于展示的組件伞辛,我們一般不會(huì)在 Components 中發(fā)起網(wǎng)絡(luò)請求烂翰、進(jìn)行本地存儲(chǔ)等操作。
而 Containers 指的是一個(gè)小的模塊蚤氏,用來連接數(shù)據(jù)并且可以修改數(shù)據(jù)的組件甘耿。Containers 一般會(huì)通過組合 Components 來使用,我們會(huì)在其中處理用戶輸入竿滨、狀態(tài)變化佳恬,也可以發(fā)起網(wǎng)絡(luò)請求等操作。
Components 和 Containers 的最簡單的區(qū)分方法是:有沒有使用 React-Redux 的 connect()
連接組件于游。
而 redux 包下存放的當(dāng)然就是和 Redux 相關(guān)的內(nèi)容啦毁葱,actions 下存放 Action Creator,reducers 下存放 reducer贰剥,最外面的 store.js 則是創(chuàng)建 store 的地方了倾剿。
Demo
看代碼之前先看下我們的 demo 實(shí)現(xiàn)的效果吧:Change Text Demo
根據(jù)最近學(xué)到的 SSCCE 原則,我盡可能把這個(gè) demo 寫的足夠簡單蚌成,目的是幫助理解 React Redux 的使用前痘。
ChangeableText
這個(gè) Component 的作用很簡單——顯示文字,不過文字和顏色是可以改變的担忧。
export default class ChangeableText extends Component {
render() {
return (
<Text style={{color: this.props.color, borderWidth: 2, padding: 8}}>{this.props.text}</Text>
);
}
}
Main
而作為 Container 的 Main
則組合了 ChangeableText
組件并且有兩個(gè)可點(diǎn)擊的按鈕用于改變文字芹缔。
class Main extends Component {
render() {
return (
<View style={styles.container}>
{/* 將從 state 映射而來的 props 傳入 Component 中,state 發(fā)生變化則 props 的值也會(huì)相應(yīng)地發(fā)生改變 */}
<ChangeableText {...this.props}/>
{/* 此時(shí)直接調(diào)用 props 上的 dispatcher */}
<TouchableOpacity style={styles.changeText} onPress={() => this.props.actions.changeText('Hello', 'red')}>
<Text>改變文字</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.props.actions.changeBack()}>
<Text>恢復(fù)</Text>
</TouchableOpacity>
</View>
);
}
}
最重要的部分就是 connect()
方法啦涵妥,這里我使用了 bindActionCreators()
來創(chuàng)建 dispacher:
// 將 state 映射成 props乖菱,這里我們可以自由組合需要使用到的 state,只要是 store 樹中存在的 state 都可以直接拿來使用
function mapStateToProps(state) {
return {
text: state.textReducer.text,
color: state.textReducer.color,
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(TextChanger, dispatch)
}
}
// 第二個(gè)參數(shù)如果是 Action Creator蓬网,則會(huì)被自動(dòng)映射成組件上的 props 作為 dispatcher 使用
export default connect(mapStateToProps, mapDispatchToProps)(Main);
Action Creators
沒啥好說的窒所,注意 type
是必須要有的。
export const CHANGE_TEXT = 'CHANGE_TEXT';
export const CHANGE_BACK = 'CHANGE_BACK';
export function changeText(text, color) {
return {
type: CHANGE_TEXT,
text,
color,
};
}
export function changeBack() {
return {
type: CHANGE_BACK,
text: 'Hi',
color: 'black',
}
}
reducers
如何組織 reducer 是門藝術(shù)帆锋,尤其是當(dāng) app 變得復(fù)雜之后吵取,不過我們現(xiàn)在不用考慮這個(gè)問題...
function textReducer(state = {text: 'Hi', color: 'black'}, action) {
let text = action.text;
let color = action.color;
// 判斷 action 類型
switch (action.type) {
case CHANGE_TEXT:
// 返回新狀態(tài)
return Object.assign({}, state, {
text: '新文字:' + text,
color: color
});
case CHANGE_BACK:
return {
...state,
text,
color
};
default:
return state;
}
}
export default textReducer;
store.js
最后就是創(chuàng)建 store 了:
const allReducers = combineReducers({textReducer});
let store = createStore(allReducers);
export default store;
Root
最最后,記得要把根組件用 <Provider>
包裝起來:
export default class Root extends Component {
render() {
return (
// 連接組件和 store
<Provider store={store}>
<Main />
</Provider>
)
}
}
OK锯厢,大致就這么簡單皮官,接下來就是運(yùn)行,debug实辑,看看 state 是怎么在組件中被傳遞的捺氢。
05 Sum Up
通過上面的介紹,配合官方文檔和 Demo剪撬,如果你有完整看完的話摄乒,相信對(duì)于如何使用 Redux 應(yīng)該基本沒問題了,所以這個(gè)時(shí)候就可以祭出這張圖了:
這張圖可以說非常完美地展現(xiàn)了 Redux 的工作原理,值得細(xì)看馍佑。其中 middleware 屬于高級(jí)內(nèi)容沒有仔細(xì)講斋否,下次有時(shí)間的話會(huì)再續(xù)更。
Redux 可以說是管理 state 的神器拭荤,能把我們從混亂和無序中解救出來茵臭。希望這篇文章可以幫助想要學(xué)習(xí) Redux 的同學(xué)盡早入門~(?????)?~~
祝大家中秋快樂!
參考: