我一個前端,今年第一份工做就是接手一個 APP 的開發。。。一個線下 BD 人員用的推廣 APP,爲了讓我這個一天原生開發都沒有學過的人能快速開發上線,因而乎就選擇了 react-native 做爲開發框架,既然主框架有了,接下來就是主要的邏輯框架的選擇了。javascript
一直以來社區裏面關於 react 的狀態管理都是推薦 Flux 思想的實踐者 Redux ,關於這個 Redux 國內已經有了不少不少的分析和講解了,天然資料也是最多,坑最少的選擇了,因此此次的開發便選擇了 Redux 全家桶來做爲整個 APP 的狀態管理庫了。前端
熟悉 Redux 的同窗必定很是熟悉這張圖了
這張經典的狀態管理流程圖清晰明確的表現出了整個 Flux 的運行流程,能夠看到全部的狀態的修改都是進過了 Dispatcher 事件,能觸發 dispatcher 的也只有 action,這樣一個流程化的過程能夠確保整個 store 的修改有據可查,咱們來看看網上都是怎麼說 flux 的好處的:vue
- 數據狀態變得穩定同時行爲可預測
- 全部的數據變動都發生在store裏
- 數據的渲染是自上而下的
- view層變得很薄,真正的組件化
- dispatcher是單例的
能夠看到,對於 flux,你們對它的評價是很是好的,的確在上面說到的方面 flux 的思路確實作的很是的好,而且讓整個狀態的改變變得規範了起來,但另一方面,這卻讓本來靈活的 JavaScript 變得 「Java」 了起來。對於一個狀態的修改咱們不得不去寫一堆模板代碼,任何狀態的修改都須要發起一個 action,而後觸發一個 dispatcher,最後才能修改到 store,而且熟悉 Redux 的同窗確定寫過這樣的代碼:java
// reduces.js let initialState = { userLogin: { name: '', state: '', payState: '', isDonor: false } }; function login(state = initialState.userLogin, action) { switch (action.type) { case GET_CODE: state.state = action.fetchData.errors ? 'GET_CODE_ERROR' : 'GET_CODE_OK'; return Object.assign({}, state, action.fetchData); case FETCH_OVER: state.payState = null; state.state = null; return Object.assign({}, state); case SUBMIT_LOGIN_FORM: state.state = action.fetchData.errors ? 'SUBMIT_LOGIN_ERROR' : 'SUBMIT_LOGIN_OK'; state.name = action.username; return Object.assign({}, state, action.fetchData); case ADD_PAY_PASS: state.payState = action.fetchData.errors ? 'SUBMIT_LOGIN_ERROR' : ADD_PAY_PASS; return Object.assign({}, state, action.fetchData); case CHANGE_PWD: state.state = action.fetchData.errors ? 'SUBMIT_LOGIN_ERROR' : 'SUBMIT_LOGIN_OK'; return Object.assign({}, state, action.fetchData); default: return state } } // action.js export function handleSubmit(values) { const {name, pass, code} = values; return dispatch => { webApi.postApi('api/accounts/login', { account: name, password: pass, code: code }, (err, data) => { let fetchData = {}; if (data.status === GLOBAL_MSG.reqSucCode) { fetchData = data; } else { fetchData.errors = data.errors[0]; } return dispatch({type: SUBMIT_LOGIN_FORM, fetchData, username: name}); }); } } // view/login.js handleSubmit(e) { e.preventDefault(); const {form, handleSubmit} = this.props; form.validateFields((err, values) => { if (!err) { handleSubmit(values); } }); }
上面的 demo 代碼只是一個片斷,它是一個簡單的登陸功能,能夠看到,嚴格的規範致使咱們須要爲了修改動一個狀態就要調用起碼三個文件裏面的內容,若是你的項目很大,或者狀態修改分的很細,那麼你面對的就不是三個文件了,可能會是更多的文件,固然,你也能夠把它們全都寫在一塊兒。。若是你的同事不砍你的話。。。react
還有一個問題,那就是異步修改的問題,這個異步發起的狀態是在 action 裏面呢仍是在 dispatcher 裏面呢?那麼就真的沒有更好的解決辦法了嘛?哪怕是語法糖也好呀。git
因此,如今在 github 上面出現了不少的「最佳實踐」,每一個團隊都拿出了本身的解決辦法,因而咱們有了不少現成的解決方案,最後咱們選擇了阿里家的 D.Va 來解決咱們工做中遇到的上述問題,github
下面咱們就來聊聊這個「最佳實踐」--Dva,來看看 dva 的 demo 是怎麼寫的吧:web
import React from 'react'; import dva, { connect } from 'dva'; import { Route } from 'dva/router'; // 1. Initialize const app = dva(); // 2. Model app.model({ namespace: 'count', state: 0, reducers: { ['count/add' ](count) { return count + 1 }, ['count/minus'](count) { return count - 1 }, }, }); // 3. View const App = connect(({ count }) => ({ count }))(function(props) { return ( <div> <h2>{ props.count }</h2> <button key="add" onClick={() => { props.dispatch({type: 'count/add'})}}>+</button> <button key="minus" onClick={() => { props.dispatch({type: 'count/minus'})}}>-</button> </div> ); }); // 4. Router app.router(<Route path="/" component={App} />); // 5. Start app.start(document.getElementById('root'));
咱們能夠看到,在上述的例子中精簡了很多「囉嗦」的代碼,在正真的業務中咱們其實只須要關係 view 文件和 model 文件這兩個中的內容就能夠了。面試
5 步 4 個接口完成單頁應用的編碼,不須要配 middleware,不須要初始化 saga runner,不須要 fork, watch saga,不須要建立 store,不須要寫 createStore,而後和 Provider 綁定,等等。但卻能擁有 redux + redux-saga + ... 的全部功能。編程
這是 demo 下的一段話,確實,這對於咱們的項目組來講是很是很是有幫助的了,減小了很多的工做量,在我開發 react-native 應用的時候,整個的開發過程變得輕鬆了不少,我不須要寫那麼多代碼了,有了參考我能夠很無腦的堆砌業務代碼了,並且不太須要關心邏輯的性能等等各類各樣的雜事。這彷佛完美的解決了我遇到的全部問題。。。
但是真的是這樣的嘛?我真的須要 Redux 嘛?我到底是須要它什麼好處呢?若是我本身管理 stroe 就不能夠嘛?固然是能夠的了,並且最先我就是這麼作的,那麼咱們再回過頭來看看 Flux 所帶來的那幾個好處。
首先是狀態可回溯,由於函數式編程中提到的反作用的緣由,每次對 store 的修改都應該是舊 store 的一個拷貝,兩個 store 是獨立的,這樣就能夠像快照同樣的保存住每一個時間段 store 的狀態來了,這樣若是咱們須要回滾到以前某一個狀態就會變得很是的容易,那麼,這個時候就出現了一個面試時的一個考點了,深拷貝和淺拷貝的問題,很顯然,新人在對 store 作修改的時候應該都會寫過這樣的代碼吧:
var newStore = OldStore.xxx;
若是你寫過這樣的代碼,那麼你會發現你依舊沒法實現狀態的回溯,由於舊的 store 仍是被改變了,那麼有的小夥伴就說了,我能夠用深拷貝呀,可是深拷貝須要在堆中從新分配內存,而且把源對象全部屬性都進行新建拷貝,才能保證拷貝後的對象與原來的對象徹底隔離,互不影響。這樣作帶來的一個問題就是內存的增長,能夠大膽的想象一下若是你的應用狀態很是的多,或者作了很是多的操做以後,內存會變成什麼樣。
這個時候你又說了,那我能夠用 immutable.js 啊,沒錯~Redux 全家桶又迎來了一位新的成員,因而你又四處去找immutable.js的資料回來看,這麼不討論immutable.js的重要性,在對於不可變數據的處理上它的確是一個不錯的選擇,但反過來思考一下,我真的須要不可變數據嘛?數據可變又會怎麼樣呢?這個時候你又要說了:廢話,沒有不可變數據我怎麼回溯狀態!
每一個業務都有本身的需求點,放在我開發的項目上,實際上是不須要這個功能的,那麼換句話說,在我這裏,我並不須要這個功能,狀態回溯功能我我的認爲是屬於一個錦上添花的功能,而不是一個剛需,固然若是你的業務須要這個特性,就當我沒說~
接下來咱們聊聊狀態的改變,flux 讓咱們經過 action 去觸發 dispatch 來修改狀態,這對於那些複雜巨大的項目來講是很是有必要的,由於那麼多文件,那麼多狀態,單靠人力去維護是很是困難的,可是這裏又有個問題了,究竟多大的項目叫大項目呢?上萬行的代碼?仍是上百個文件?你的項目真的達到了這個程度了嘛?若是是,那麼再想一想若是優化一下代碼邏輯,項目結構,還會有這麼大的代碼量嗎?在這裏說個不太恰當的例子,我剛參加工做的時候一個功能我能寫好幾百行,而後到了 code review 的時候那些老道的同事總能用幾十行的代碼來完成和我同樣的功能,甚至比個人還要好,那你說若是一箇中小型項目全都是我這種質量的代碼去堆砌的話,它是否是就變成了一個大型項目呢?
顯然不是這樣的,一個項目的大小不單靠代碼的量去衡量,這裏面有不少方面的影響,依賴繁多、功能繁瑣、邏輯複雜。這些都是大項目的特色,若是你是 bat 大廠的同窗,那應該也不會看我這個了,而對於和我同樣的小廠開發來講,咱們手裏的項目真的是一個巨無霸嘛?我想快速迭代的需求恐怕纔是第一要解決的問題了,若是代碼書寫規範,邏輯實現優雅,結構組織清晰,很大程度上你手裏的項目是一個小而美的項目,在這種狀況下,爲何不能換個思路呢?
前段時間我看到了 mobx.js,一個新的狀態管理庫,但思路卻徹底不同,它沒有不可變數據的要求,它表明的是另一種開發思路,使用 Mobx,代碼會更加清晰,也便於維護,固然前提是你的項目是一箇中小型項目,其實用過 vue 的同窗去看它,就會以爲至關的親切,對於 mobx 的資料如今網上也有不少了,我在這裏就不作過多的介紹了,前段時間我抱着試試看的心態,嘗試着重構了app 中的部分代碼,相較於 dva 模式下的代碼,確實精簡了很多,也由此看來個人項目中充斥着大量的「冗餘」代碼,從而看起來很是的龐大,經過精簡邏輯,優化結構,採用 mobx 反而更「優雅」了。
雖然最後一次的重構並無上線我就離職了,可是也是這一次的重構讓我認識到了你們說的「最佳實踐」並不必定就是我項目的「最佳實踐」,不少時候仍是要考慮自身的侷限性。面對前端豐富的解決方案,如何選擇時候本身的纔是咱們這些開發者須要考慮的,不一樣的場景有不一樣的語言,一樣不一樣的業務也有不一樣的框架,曾經我覺得能夠 redux 全家桶一桶走天下的,可是如今想一想仍是「too young too simple sometimes native」啊。如今看看那些招聘或者求職上清一色寫的 Redux,不妨仔細想一想,真的須要嗎?