初期參加工作開發專案時,使用React Native + Flux進行手機應用跨平臺開發,在上一篇博文中資料流架構學習筆記(一)-Flux 對Flux做了一次總結,本文是我對資料流管理架構學習總結的第二篇資料流架構學習筆記(二)-Redux,是我在工作過程中對專案使用Redux進行重構狀態管理和資料流的學習記錄。
Redux的由來
2014年 Facebook 提出了 Flux 架構的概念和單向資料流管理的思想,並給出了管理狀態的基本資料流,但是隨著前端應用的複雜性指數級的提升,前端頁面需要管理的狀態也越來越多,於是出現了很多基於Flux基本的資料流概念和單向資料流思想的實現方式。2015年,Redux 出現,將 Flux 與函數語言程式設計結合一起,很短時間內就成為了最熱門的前端架構。
在實際專案中,你應該有遇到過以下這樣情況的發生:
- 在debug專案進行問題查詢時,複雜的資料重新整理頁面時,由於一些不規範的監聽和觀察者機制使用或過多使用React中this.setState進行渲染頁面,由於是非同步進行資料渲染頁面,經常無法判斷本次造成是因為什麼資料狀態改變而造成頁面渲染,而且更可惡的是此時你無法知道當前狀態下,你的App實際的資料狀態是如何的,就是說,你無法知道你App當前的所有資料是多少,而你同樣也無法快速預測接下來你的App會如何變化。使用Redux就可以很好的解決這個問題。
- 同樣的如果你還在專案中進行模組劃分,元件化開發,使用Redux可以快速將你的模組元件進行併入專案和拆分重組。
Redux工作原理
Redux 把自己標榜為一個“可預測的狀態容器 ”,它充分利用函式式的特性,讓整個實現更加優雅純粹,使用起來也更簡單。
Redux(oldState) => newState複製程式碼
Redux 可以看作是 Flux 的一次進化。Redux遵循以下三個基本原則:
- 整個應用只有唯一一個可信資料來源,也就是隻有一個 Store
- State 只能通過觸發 Action 來更改
- State 的更改必須寫成純函式,也就是每次更改總是返回一個新的 State,在 Redux 裡這種函式稱為 Reducer
View 觸發資料更新 —> Actions 將資料傳遞到 Store —> Store 更新 state —> 更新 View。複製程式碼
Redux 中整個應用的狀態儲存在一顆 object tree 中,對應一個唯一的 Store,並且 state 是隻讀的,使用純函式 reducer 來更新 state 會生成一個新的 state 而不是直接修改原來的。
Redux 通過以上約束讓 state 的變化可預測。
如果無法理解這些概念,建議先學習Redux官方文件,再來檢視他人的部落格和使用方式,才能更快的使用。
Redux例項封裝
這裡以React Native實際專案登入部分展示如何將Redux應用到React Native開發中進行資料管理,在實際架構專案時,每個人有各自的編碼習慣,因而,雖然同樣是Redux,但是在各部分程式碼寫法總是有所不一樣,而實際專案中用起來的寫法也是不一樣的,但是思想總體上是一樣的,不要拘泥於程式碼的寫法,程式碼只是作為參考和總結,應該理解寫法的目的思想和如何體現Redux的融入和使用。
檢視層View
登入頁:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import LoginAction from '../../actions/loginAction';
...
class Login extends Component {
...
_doLogin = () => {
const param = {
uid: this.state.uid,
pwd: this.state.pwd
};
this.props.actions.doLogin(param)
.then(() => {
const { navigation, login } = this.props;
if (login.status === 'done' && navigation) {
navigation.resetRouteTo('TabBar', { title: '首頁', selectedTab: 'home' });
} else {
Alert.alert(
'提示',
login.message
);
}
});
};
...
}
...
const mapStateToProps = (state) => {
return {
login: state.loginReducer
};
};
const mapDispatchToProps = dispatch => {
return ({
actions: bindActionCreators({ ...LoginAction }, dispatch)
});
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);複製程式碼
簡單說View層主要作用就是響應使用者的操作,而實際在程式碼中我們主要的作用就是觸發Action,如程式碼中呼叫this.props.actions.doLogin()函式,在this.props會存在actions屬性是由於在最後使用bindActionCreators方法將對應的LoginAction繫結至頁面元件Login中,這樣造成我在View層只會做呼叫action的操作,不會直接使用dispatch進行訊息分發。這樣就完成了View -> Actions
的過程。
行為Action
const _loginSuccess = (data) => {//eslint-disable-line
return {
type: ActionTypes.LOGIN_SUCCESS,
payload: {
user: data.uid
}
};
};
const _loginFailed = (error) => {
return {
type: ActionTypes.FAIL,
payload: {
message: error.message
}
};
};
const _doLogin = (url, param) => dispatch => {
dispatch(CommonAction.showLoading());
return Fetcher.postQsBodyFetch(url, param)
.then((response) => {
dispatch(CommonAction.dismissLoading());
dispatch(_loginSuccess(param, response));
}).catch((error) => {
dispatch(CommonAction.dismissLoading());
dispatch(_loginFailed(error));
});
};
const LoginAction = {
doLogin: (param) => _doLogin(NetLink.login, param),
loginSuccess: (data) => _loginSuccess(data),
loginFailed: (error) => _loginFailed(error),
};複製程式碼
Action通常都是在進行網路層呼叫、請求資料和分發資料,因在View層使用了bindActionCreators方法和元件繫結後,將會直接獲取View層元件dispatch屬性方法,使得在Action的純函式中在資料返回後呼叫dispatch()進行資料分發。這樣就完成了Actions -> Reducer
的過程。
Reducer
import ActionType from '../constants/actionType';
const initialState = {
status: 'init',
user: '',
message: null
};
const loginReducer = (state = initialState, action) => {
switch (action.type) {
case ActionType.LOGIN_SUCCESS:
return Object.assign({}, state, {
status: 'done',
user: action.payload.user,
});
case ActionType.FAIL:
return Object.assign({}, state, {
status: 'fail',
message: action.payload.message,
});
default:
return state;
}
};複製程式碼
Reducer類似原來Flux的store,作為資料倉儲來源,這裡將會收到來自呼叫dipatch()後獲得的訊息,並進行處理和儲存,並在及時更新資料後,通過redux的元件繫結,自動反饋至頁面元件中進行資料更新和非同步渲染,而在這裡你應該return一個全新的物件,redux才能知道你是更新了當前元件關聯的reducer,到了這一步你應該會產生疑問,資料狀態是如何反饋至View,而你寫的普普通通的Action和Reducer等js檔案是如何關聯上你的元件和應用。
繫結和引入
入口元件Root.js:
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import Fetcher from './network/fetcher';
import Main from './containers/mainContainer';
import rootReducer from './reducers/rootReducer';
const middlewares = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
const createLogger = require('redux-logger');
if (process.env.NODE_ENV === 'development') {
const logger = createLogger();
middlewares.push(logger);
}
function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState);
return store;
}
const store = configureStore();
export default class Root extends Component {
constructor(props) {
super(props);
Fetcher.initNetworkState();
}
componentWillUnmount() {
Fetcher.removeNetworkStateListener();
}
render() {
return (
<Provider store={store}>
<Main {...this.props} />
</Provider>
);
}
}複製程式碼
其中rootReducer.js:
import { combineReducers } from 'redux';
import LoginReducer from './loginReducer';
...
const rootReducer = combineReducers({
loginReducer: LoginReducer,
...
});
export default rootReducer;複製程式碼
先使用combineReducer將所有的reducer合併成一個rootReducer,使用rootReducer,在接著開始通過createStore方法建立了store物件,通過redux提供的Provider元件直接將store物件繫結至實際的View元件上,這樣就完成了
View 觸發資料更新 —> Actions 將資料傳遞到 Store —> Store 更新 state —> 更新 View。複製程式碼
關於其中的applyMiddleware
,bindActionCreators
等等方法是關於非同步actions、非同步資料流、Middleware的進階知識。具體內容建議檢視Redux官方文件瞭解相關內容。以下Redux進階也將會大概講解他們的使用和為什麼使用。
Redux進階
使用redux-thunk和redux-logger框架,並使用applyMiddleware和Middleware來加強Redux的使用,使Redux更加強大、規範和合理。其中redux-logger框架簡單理解就是新增一個Redux日誌列印和處理框架,開發者無需知道他如何規範列印出Redux的dispatch日誌的,但是在開發時,用於debug等是很有用的工具。redux-thunk屬於處理非同步Actions和非同步資料流的框架。
非同步Actions和非同步資料流
什麼是非同步Actions和非同步資料流,簡單來說,就是網路請求來控制的Actions。如App中你點選一個按鈕立即發出了一個dispatch(), 這是你對App控制作出的dispatch(), 這叫做同步Actions,而非同步Actions並不是你控制的,如網路請求成功或失敗後,才會發出一個dispatch(),這就是非同步Actions,你無法知道這個dispatch()是什麼時間做出的操作,也不知道你發出的是成功的dispatch()或是失敗的dispatch()。
如我loginAction中方法,現在應該就能很好的理解這個方法這樣寫的原理:
const _doLogin = (url, param) => dispatch => {
dispatch(CommonAction.showLoading());
return Fetcher.postQsBodyFetch(url, param)
.then((response) => {
dispatch(CommonAction.dismissLoading());
dispatch(_loginSuccess(param, response));
}).catch((error) => {
dispatch(CommonAction.dismissLoading());
dispatch(_loginFailed(error));
});
};複製程式碼
Middleware
middleware翻譯成中文意思中介軟體,很貼切也很容易理解,像redux-thunk 或 redux-promise就可以叫做中介軟體,如果你想使用這些中介軟體,就需要使用applyMiddleware等等相關方法為你的專案新增上這些框架。使專案使用redux更加強大和規範。
像redux-thunk 或 redux-promise 這樣支援非同步的 middleware 都包裝了 store 的 dispatch() 方法,以此來讓你 dispatch 一些除了 action 以外的其他內容,例如:函式或者 Promise。你所使用的任何 middleware 都可以以自己的方式解析你 dispatch 的任何內容,並繼續傳遞 actions 給下一個 middleware。比如,支援 Promise 的 middleware 能夠攔截 Promise,然後為每個 Promise 非同步地 dispatch 一對 begin/end actions。
當 middleware 鏈中的最後一個 middleware 開始 dispatch action 時,這個 action 必須是一個普通物件。這是 同步式的 Redux 資料流 開始的地方(譯註:這裡應該是指,你可以使用任意多非同步的 middleware 去做你想做的事情,但是需要使用普通物件作為最後一個被 dispatch 的 action ,來將處理流程帶回同步方式)。
middleware 可以完成包括非同步 API 呼叫在內的各種事情,瞭解它的演化過程是一件相當重要的事。而他們是如何演化過來的,並如何加強你的應用的,這裡不再具體說明。
總結
Redux很強大,也相對複雜。簡單的專案也許並不需要使用到,但是如果你的專案越來越大,資料越來越複雜,Redux將會使你專案更加規範和健壯。在專案中使用規範的框架架構,是一件非常重要的事情,你可以為你的專案使用架構,這是一件很有趣的事情。
文章很長,Redux也很複雜,文中如有不對請告知,我會及時改正。一起進步和學習。謝謝!