這篇文章是作為一個新手在學習過程中對Redux概念的理解。有錯誤的地方希望各位能幫我指出。一起學習進步。
使用React native 開發的時候,一直在糾結是否要使用Redux,剛接觸的時候會發現又有action,又有Reducer,還有dispatch,對于我這類剛剛從移動開發學習RN開發的人,很容易就搞的一團霧水,學習Redux也是看了好幾遍然后又放棄了很多遍,知道公司業務需求開發狀態比較復雜的項目的時候,終于下定決心學習使用Redux。
學習下來后發現其實最難入門的是理解Redux的概念和思想,理解后會覺得入門的難度降低很多。所以分享一下我這個剛入門人的體會。
理解Redux
我們在開發React 或者React native的時候,在一個compentent中,一般的做法是通過state來定義一些可能會引起頁面變化的變量。比如登錄狀態,或者<Text/>
中的文字等。通過改變state狀態來刷新頁面,state改變的時候會觸發render來重新渲染該頁面。
如果我們要通過改變state來刷新子控件的話。可以設置子控件的Props為當前控件的state<SubComponent someProps={this.state.someState} />
,通過改變state來控制頁面刷新。或者調用子控件ref來使用子控件的方法。
然后再跨控件的話。比如相隔了好幾個頁面。就只能通過設置global全局變量或者使用通知監聽的方式來實現界面刷新。global是開發過程中非常不推薦的方式。而如果一個狀態的改變要刷新很多其他頁面的UI。比如登錄成功的狀態要刷新個人中心頁面其他頁面的頭像或者其他等等時。就會要寫很多的監聽。
而且react native中的componentWillMount和iOS中的viewWillAppear不同。切換頁面前后是不會觸發,componentWillUnmount也相當于iOS中的delloca(釋放內存時才觸發)。所以必定要使用到很多監聽或global。如果這樣改變頁面多的state多的話,整個應用就會變得非常亂。因此Redux的作用在此時就可以顯現出來了。
Redux的概念大概是這樣的。把整個應用看成一個component的話,這整個應用有一個對應的this.state。所有頁面都可以拿到這個state中的內容。當這個this.state中的state改變的時候。所有關聯頁面的頁面就會刷新。這樣,不用寫global,不用寫通知。就可以跨頁面共享一個狀態。畢竟所有redux中的狀態都是可以全局拿到的。
有點像上面提到的<SubComponent someProps={this.state.someState} />
改變state的來改變子控件UI的做法。所有用到rudux的頁面都是他的子控件。改變了state子頁面就都會跟這變。
Store
首先我們要理解Store。這個就理解為所有狀態的容器就行。它就是套在所有子頁面外的頂級Component。所有的Redux狀態都是存在這個頂級的component中的this.state。它通過<SubComponent someProps={this.state.someState} />
這樣的方式來控制子空間的UI。改變了Store中的state就可以實現我們改變UI的操作。
我們現在把所有數據都存在了store中。那么下一步。如果我們要怎么改變Store中的state。調用類似Store.setState({...})
這樣的操作嗎?這樣顯得太隨意也太簡單了。畢竟Store中的數據可能影響整個程序。如果某個頁面誤改變一下就會引起很多頁面的同時變動。出了錯誤也很難定位到是哪邊改變出的問題。State應該設置為只讀的。
所以Store中state這個值不能直接改變。我們需要一個比較完善的系統來管理所有的state。這時候就該action和reducer出手了。
Action
reducer規定,如果想改變這個Store中的狀態。不能直接改變他的值。而是要要寫一個叫action的東西。
那么action怎么樣簡單的理解呢。就是它字面上的意思。一個動作。比如我要登錄。那么登錄成功或者登錄失敗就是一個動作。怎么描述這個動作呢?一般會用下面的一個對象來描述
//定義一個action的種類
const ACTION_TYPE_LOGIN = 'action_type_login';
const loginSuccessAction = {
type:ACTION_TYPE_LOGIN,
Result:'success'
}
const loginFailedAction = {
type:ACTION_TYPE_LOGIN,
Result:'failed'
}
這樣loginSuccessAction
和loginFailedAction
就分別代表了登錄成功和登錄失敗這個一個動作。type
代表著action的種類。成功和失敗都可以歸結為登錄這個種類。 type
是必須要的參數,后面的Reducer會根據這個屬性來判斷改變哪部分的狀態。
dispatch
字面上的意思是部署。部署什么呢?部署一個動作。也就是告訴Redux。我登錄成功了。你趕緊處理一下改變相應State吧。告訴Redux。使用起來就是調用dispatch(loginSuccessAction)
,然后,Redux就知道你登錄成功了。他就會讓一個叫Reducer的人來處理你這個動作。
Reducer
我們又碰到了一個新的名詞Reducer。這是什么東西呢?Reducer可以理解為通過接收action動作,然后來改變我們的全局狀態的一個處理者。
通過dispatch(loginSuccessAction)
來告訴Redux,我登錄成功了。然后rudux就讓Ruducer來處理這個登錄成功動作,Reducer拿到了登錄成功這個動作,同時也拿到了Store中當前的狀態。通過這兩個對象。生成了一個登錄成功后Store應該變成的狀態。
用代碼表示就是這樣的
function loginReducer (state,action){
switch (action.type) {
case TYPES.ACTION_LOGIN: // 初始狀態
return Object.assign({}, state, {
result:action.Result
});
case ...: // 初始狀態
default:
return state;
}
這里要注意的是Reducer并不是用來改變Store中的狀態的。他是通過Action來生成一個新的狀態的。然后return 給了Store,Store自己再替換自己的狀態。
所以總結下來各一句話就是
Store:狀態容器
Action:對于一個動作的描述,是一個對象。
dispatch:實施動作的方法。
reducer:接收一個action和Store的當前狀態,返回新的Store該有狀態的處理者。
Redux的最基本的概念差不多就是這樣。
稍作進階
上面的介紹完后,應該對Rudux的概念有了一些初步理解。那么我們要應用到項目中,還需要一些性能的處理和優化。
中間件Middleware
如果按照上面的步揍,我們完成登錄操作要寫多少個Action呢?至少有這些吧
登錄成功,登錄失敗,登錄錯誤,登錄中。那我們就要寫四個Action。那整個項目要寫的action會突破天際吧。這時候。對于屬于一個type
的action。我們可以把他們四合一,寫一個生成action的方法然后執行就行.
function loginAction(result){
return {
type:ACTION_LOGIN,
result:result
}
}
然后調用store.dispatch(loginAction(result))
就可以了。
那么我們登錄過程要在component中通過登錄的result來dispatch好幾次。可不可以把判斷的過程也剝離出來呢?比如整個登錄我只會在component中調用一次store.dispatch(login()),后面所有的登錄結果的改變全都不在頁面里處理。這樣頁面就可以只負責UI。狀態的改變完全交給Redux。
login預想的方法是這樣
function login(){
//這里就沒法返回登錄中的action
fetch(url).then(result=>{
return{
type:action_login,
result:result
}
}).catch(err=>{
return{
type:action_login,
result:'error'
}
})
}
但是store.dispatch()默認是只接收一個Action對象。而我們login()方法中肯定要涉及到異步操作。action對象不能立即返回出來。store.dispatch(login())在執行的時 login()不能直接有返回值。相當于執行了store.dispatch(null)。而且登錄的整個流程要返回登錄中+登錄結果兩個action,這樣構造肯定沒法實現。怎么解決這個問題呢?
如果我們能在login()中執行其他的dispatch()動作是不是就迎刃而解了。
如果能寫成這樣
function login(){
//返回一個方法
return dispath=>{
dispatch(login(loginAction('logging in')));
fetch(url).then(result=>{
dispatch(login(loginAction(result)));
}).catch(err=>{
dispatch(login(loginAction('error')));
})
}
}
function loginAction(result){
return {
type:ACTION_LOGIN,
result:result
}
}
但是redux默認是不允許這么寫的。因為 return的 dispath=>{...}是一個方法而不是對象。我們可以通過安裝一個中間件redux-thunk
來實現讓store.dispatch()可以接收一個方法。而不是只接收action對象。
出現了一個新名詞,中間件(middleware)。
Redux的流程其實很簡單
store.dispatch(action) -> reducer處理action,返回一個新的state ->Store更新state ->相關UI更新。
middleware可以認為就是改造store.dispatch()
這個方法的。他讓你在執行部署action的過程中,加入一些自己的處理。比如redux-logger
這個中間件可以用來添加日志功能,在dispatch()時打印出你所執行的所有動作。redux-trun
可以讓dispatch()接受一個function。redux-promise
可以讓其接收一個promise.
模塊分割來優化性能
在component中,如果改變this.state中的任意一個狀態,都會引發頁面的render渲染。
Redux在一個應用中只允許有一個Store 容器來存儲State。那么所有的State就會都在這個Store中。也就是說。如果改變了state的話。整個應用中用到store中的state的頁面都會隨之刷新。可想而知。不進行優化的話,必定會有很大的性能問題。
想到的第一個方法是shouldComponentUpdate
攔截不必要的Render。
shouldComponentUpdate(nextProps,nextState){
if(nextProps.ReduxState.loginResult != this.Props.ReduxState.loginResult){
return true
}
return false
}
但是這樣的話需要我們逐個判斷props改變是否跟新頁面。全都這樣判斷稍大點的應用得寫幾千行代碼。
這時候。我們需要把Rudux分模塊切割開來。每個小模塊只負責他負責的事情。UI的component用到哪個模塊就把這個模塊和它關聯起來。
切割模塊
import {combineReducers} from "redux";
import ...
const rootReducer = combineReducers({
LoginReducer,
PageManagerReducer,
MusicManagerReducer
...
});
然后UI 頁面中中
class Page extends React.Component {
login = ()=>{
//調用Action的方法
this.props.dispatch(...someAction)
}
render(
<View>
<Text>{this.props.LoginReducer.someState}</Text>
</View>
)
}
const useReducers = store => {
return {
//store.LoginReducer就是此頁面使用到的Reducer 就是rootReducer中的模塊名,前面的LoginReducer代表的是頁面使用時this.props中的參數名。
LoginReducer: store.LoginReducer,
};
};
//關聯頁面和用到的模塊,關聯后,store就會把子模塊的State通過props傳遞給此頁面。this.props
export default connect(useReducers)(Page);
這樣UI頁面中,只有在LoginReducer子模塊中的狀態發生改變時,頁面才會刷新。
好了,掌握這些東西。我們就可以開始用Redux寫React native應用了。
中間有很多代碼示例沒有寫。晚點直接寫個Demo吧。
學習的時候推薦阮一峰老師的Redux系列教程。實在和我一樣概念不清的再來看看我個人的描述吧。
傳送門:http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html