我一个前端,今年第一份工做就是接手一个 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,不妨仔细想一想,真的须要吗?