在過去一年,愈來愈多的項目繼續或者開始使用React和Redux開發,這是目前前端業內很廣泛的一種前端項目解決方案,可是隨着開發項目愈來愈多,愈來愈多樣化時,我的又有了不一樣的感覺和想法。是否是由於已經有了一個比較廣泛的,熟悉的項目技術棧,咱們就一直徹底沿用呢,有沒有比他更適合的方案呢?恰逢團隊最近有一個新項目,因而博主開始思考,有沒有可能使用其餘可替代技術開發呢?既能提升開發效率,又能拓展技術儲備和眼界,通過調研,選擇了Mobx,最終使用React+Mobx搭建了新項目,本篇總結分享從技術選型到項目實現的較完整過程,但願共同進步。javascript
歡迎訪問個人我的博客html
當咱們使用React開發web應用程序時,在React組件內,可使用this.setState()
和this.state
處理或訪問組件內狀態,可是隨着項目變大,狀態變複雜,一般須要考慮組件間通訊問題,主要包括如下兩點:前端
關於這些問題,React組件開發實踐推薦將公用組件狀態提高:java
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestorreact
一般多組件須要處理同一狀態,咱們推薦將共享狀態提高至他們的共同最近祖先組件內。更多詳情查看web
當項目愈加複雜時,咱們發現僅僅是提高狀態已經沒法適應如此複雜的狀態管理了,程序狀態變得比較難同步,操做,處處是回調,發佈,訂閱,這意味着咱們須要更好的狀態管理方式,因而就引入了狀態管理庫,如Redux,Mobx,Jumpsuit,Alt.js等。編程
狀態管理庫,不管是Redux,仍是Mobx這些,其本質都是爲了解決狀態管理混亂,沒法有效同步的問題,它們都支持:redux
react-redux
,mobx-react
;一般使用狀態管理庫後,咱們將React組件從業務上劃分爲兩類:
目前來看,Redux已經是React應用狀態管理庫中的霸主了,而Mobx則是一方諸侯,咱們爲何要選擇Mobx,而不是繼續沿用Redux呢,那就須要比較他們的異同了。後端
Mobx和Redux都是JavaScript應用狀態管理庫,都適用於React,Angular,VueJs等框架或庫,而不是侷限於某一特定UI庫。react-router
要介紹Redux,咱們就不得不談到Flux了:
Flux is the application architecture that Facebook uses for building client-side web applications.It's more of a pattern rather than a formal framework
Flux是Facebook用來開發客戶端-服務端web應用程序的應用架構,它更可能是一種架構模式,而非一個特定框架。詳解Flux。
而Redux更多的是遵循Flux模式的一種實現,是一個JavaScript庫,它關注點主要是如下幾方面:
redux-thunk
,redux-saga
等;Mobx是一個透明函數響應式編程(Transparently Functional Reactive Programming,TFRP)的狀態管理庫,它使得狀態管理簡單可伸縮:
Anything that can be derived from the application state, should be derived. Automatically.
任何起源於應用狀態的數據應該自動獲取。
其原理如圖:
Action:定義改變狀態的動做函數,包括如何變動狀態;
Store:集中管理模塊狀態(State)和動做(action);
Derivation(衍生):從應用狀態中派生而出,且沒有任何其餘影響的數據,咱們稱爲derivation(衍生),衍生在如下狀況下存在:
用戶界面;
衍生數據;
衍生主要有兩種:
import {observable, autorun} from 'mobx';
var todoStore = observable({
/* some observable state */
todos: [],
/* a derived value */
get completedCount() {
return this.todos.filter(todo => todo.completed).length;
}
});
/* a function that observes the state */
autorun(function() {
console.log("Completed %d of %d items",
todoStore.completedCount,
todoStore.todos.length
);
});
/* ..and some actions that modify the state */
todoStore.todos[0] = {
title: "Take a walk",
completed: false
};
// -> synchronously prints: 'Completed 0 of 1 items'
todoStore.todos[0].completed = true;
// -> synchronously prints: 'Completed 1 of 1 items'
複製代碼
Redux更多的是遵循函數式編程(Functional Programming, FP)思想,而Mobx則更多從面相對象角度考慮問題。
Redux提倡編寫函數式代碼,如reducer就是一個純函數(pure function),以下:
(state, action) => {
return Object.assign({}, state, {
...
})
}
複製代碼
純函數,接受輸入,而後輸出結果,除此以外不會有任何影響,也包括不會影響接收的參數;對於相同的輸入老是輸出相同的結果。
Mobx設計更多偏向於面向對象編程(OOP)和響應式編程(Reactive Programming),一般將狀態包裝成可觀察對象,因而咱們就可使用可觀察對象的全部能力,一旦狀態對象變動,就能自動得到更新。
store是應用管理數據的地方,在Redux應用中,咱們老是將全部共享的應用數據集中在一個大的store中,而Mobx則一般按模塊將應用狀態劃分,在多個獨立的store中管理。
Redux默認以JavaScript原生對象形式存儲數據,而Mobx使用可觀察對象:
Redux狀態對象一般是不可變的(Immutable):
switch (action.type) {
case REQUEST_POST:
return Object.assign({}, state, {
post: action.payload.post
});
default:
retur nstate;
}
複製代碼
咱們不能直接操做狀態對象,而老是在原來狀態對象基礎上返回一個新的狀態對象,這樣就能很方便的返回應用上一狀態;而Mobx中能夠直接使用新值更新狀態對象。
使用Redux和React應用鏈接時,須要使用react-redux
提供的Provider
和connect
:
Provider
:負責將Store注入React應用;connect
:負責將store state注入容器組件,並選擇特定狀態做爲容器組件props傳遞;對於Mobx而言,一樣須要兩個步驟:
Provider
:使用mobx-react
提供的Provider
將全部stores注入應用;inject
將特定store注入某組件,store能夠傳遞狀態或action;而後使用observer
保證組件能響應store中的可觀察對象(observable)變動,即store更新,組件視圖響應式更新。redux-thunk
或redux-saga
編寫額外代碼,Mobx流程相比就簡單不少,而且不須要額外異步處理庫;@observable
and @observer
,以面向對象編程方式使得JavaScript對象具備響應式能力;而Redux最推薦遵循函數式編程,固然Mobx也支持函數式編程;接下來咱們使用Redux和Mobx簡單實現同一應用,對比其代碼,看看它們各自有什麼表現。
在Redux應用中,咱們首先須要配置,建立store,並使用redux-thunk
或redux-saga
中間件以支持異步action,而後使用Provider
將store注入應用:
// src/store.js
import { applyMiddleware, createStore } from "redux";
import createSagaMiddleware from 'redux-saga'
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from "./reducers";
import App from './containers/App/';
const sagaMiddleware = createSagaMiddleware()
const middleware = composeWithDevTools(applyMiddleware(sagaMiddleware));
export default createStore(rootReducer, middleware);
// src/index.js
…
ReactDOM.render(
<BrowserRouter> <Provider store={store}> <App /> </Provider> </BrowserRouter>,
document.getElementById('app')
);
複製代碼
Mobx應用則能夠直接將全部store注入應用:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'mobx-react';
import { BrowserRouter } from 'react-router-dom';
import { useStrict } from 'mobx';
import App from './containers/App/';
import * as stores from './flux/index';
// set strict mode for mobx
// must change store through action
useStrict(true);
render(
<Provider {...stores}> <BrowserRouter> <App /> </BrowserRouter> </Provider>,
document.getElementById('app')
);
複製代碼
Redux:
// src/containers/Company.js
…
class CompanyContainer extends Component {
componentDidMount () {
this.props.loadData({});
}
render () {
return <Company infos={this.props.infos} loading={this.props.loading} /> } } … // function for injecting state into props const mapStateToProps = (state) => { return { infos: state.companyStore.infos, loading: state.companyStore.loading } } const mapDispatchToProps = dispatch => { return bindActionCreators({ loadData: loadData }, dispatch); } // injecting both state and actions into props export default connect(mapStateToProps, { loadData })(CompanyContainer); 複製代碼
Mobx:
@inject('companyStore')
@observer
class CompanyContainer extends Component {
componentDidMount () {
this.props.companyStore.loadData({});
}
render () {
const { infos, loading } = this.props.companyStore;
return <Company infos={infos} loading={loading} /> } } 複製代碼
Redux:
// src/flux/Company/action.js
…
export function fetchContacts(){
return dispatch => {
dispatch({
type: 'FREQUEST_COMPANY_INFO',
payload: {}
})
}
}
…
// src/flux/Company/reducer.js
const initialState = {};
function reducer (state = initialState, action) {
switch (action.type) {
case 'FREQUEST_COMPANY_INFO': {
return {
...state,
contacts: action.payload.data.data || action.payload.data,
loading: false
}
}
default:
return state;
}
}
複製代碼
Mobx:
// src/flux/Company/store.js
import { observable, action } from 'mobx';
class CompanyStore {
constructor () {
@observable infos = observable.map(infosModel);
}
@action
loadData = async(params) => {
this.loading = true;
this.errors = {};
return this.$fetchBasicInfo(params).then(action(({ data }) => {
let basicInfo = data.data;
const preCompanyInfo = this.infos.get('companyInfo');
this.infos.set('companyInfo', Object.assign(preCompanyInfo, basicInfo));
return basicInfo;
}));
}
$fetchBasicInfo (params) {
return fetch({
...API.getBasicInfo,
data: params
});
}
}
export default new CompanyStore();
複製代碼
若是使用Redux,咱們須要另外添加redux-thunk
或redux-saga
以支持異步action,這就須要另外添加配置並編寫模板代碼,而Mobx並不須要額外配置。
redux-saga主要代碼有:
// src/flux/Company/saga.js
// Sagas
// ------------------------------------
const $fetchBasicInfo = (params) => {
return fetch({
...API.getBasicInfo,
data: params
});
}
export function *fetchCompanyInfoSaga (type, body) {
while (true) {
const { payload } = yield take(REQUEST_COMPANY_INFO)
console.log('payload:', payload)
const data = yield call($fetchBasicInfo, payload)
yield put(receiveCompanyInfo(data))
}
}
export const sagas = [
fetchCompanyInfoSaga
];
複製代碼
不管前端仍是後端,遇到問題,大多數時候也許你們老是習慣於推薦已經廣泛推廣使用的,習慣,熟悉就很容易變成瓜熟蒂落的,咱們應該更進一步去思考,合適的纔是更好的。
固然對於「Redux更規範,更靠譜,應該使用Redux」或"Redux模版太多,太複雜了,應該選擇Mobx"這類推斷,咱們也應該避免,這些都是相對而言,每一個框架都有各自的實現,特點,及其適用場景,正好比Redux流程更復雜,但熟悉流程後就更能把握它的一些基礎/核心理念,使用起來可能更有心得及感悟;而Mobx簡單化,把大部分東西隱藏起來,若是不去特別研究就不能接觸到它的核心/基本思想,也許使得開發者一直停留在使用層次。
因此不管是技術棧仍是框架。類庫,並無絕對的比較咱們就應該選擇什麼,拋棄什麼,咱們應該更關注它們解決什麼問題,它們解決問題的關注點,或者說實現方式是什麼,它們的優缺點還有什麼,哪個更適合當前項目,以及項目將來發展。