我的前端故事----关于redux的一些思考
背景
我一个前端,今年第一份工作就是接手一个 APP 的开发。。。一个线下 BD 人员用的推广 APP,为了让我这个一天原生开发都没有学过的人能快速开发上线,于是乎就选择了 react-native 作为开发框架,既然主框架有了,接下来就是主要的逻辑框架的选择了。
一直以来社区里面关于 react 的状态管理都是推荐 Flux 思想的实践者 Redux ,关于这个 Redux 国内已经有了很多很多的分析和讲解了,自然资料也是最多,坑最少的选择了,所以这次的开发便选择了 Redux 全家桶来作为整个 APP 的状态管理库了。
疑问
熟悉 Redux 的同学一定非常熟悉这张图了
这张经典的状态管理流程图清晰明确的表现出了整个 Flux 的运行流程,可以看到所有的状态的修改都是进过了 Dispatcher 事件,能触发 dispatcher 的也只有 action,这样一个流程化的过程可以确保整个 store 的修改有据可查,我们来看看网上都是怎么说 flux 的好处的:
- 数据状态变得稳定同时行为可预测
- 所有的数据变更都发生在store里
- 数据的渲染是自上而下的
- view层变得很薄,真正的组件化
- dispatcher是单例的
可以看到,对于 flux,大家对它的评价是非常好的,的确在上面说到的方面 flux 的思路确实做的非常的好,并且让整个状态的改变变得规范了起来,但另外一方面,这却让原本灵活的 JavaScript 变得 “Java” 了起来。对于一个状态的修改我们不得不去写一堆模板代码,任何状态的修改都需要发起一个 action,然后触发一个 dispatcher,最后才能修改到 store,并且熟悉 Redux 的同学肯定写过这样的代码:
// 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 代码只是一个片段,它是一个简单的登录功能,可以看到,严格的规范导致我们需要为了修改动一个状态就要调用起码三个文件里面的内容,如果你的项目很大,或者状态修改分的很细,那么你面对的就不是三个文件了,可能会是更多的文件,当然,你也可以把它们全都写在一起。。如果你的同事不砍你的话。。。
还有一个问题,那就是异步修改的问题,这个异步发起的状态是在 action 里面呢还是在 dispatcher 里面呢?那么就真的没有更好的解决办法了嘛?哪怕是语法糖也好呀。
所以,现在在 github 上面出现了很多的“最佳实践”,每个团队都拿出了自己的解决办法,于是我们有了很多现成的解决方案,最后我们选择了阿里家的 D.Va 来解决我们工作中遇到的上述问题,
解决
下面我们就来聊聊这个“最佳实践”--Dva,来看看 dva 的 demo 是怎么写的吧:
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,不妨仔细想想,真的需要吗?
我的前端故事----关于redux的一些思考的更多相关文章
- 我的前端故事----我为什么用GraphQL
背景 今年我在做一个有关商户的app,这是一个包含商户从入网到审核.从驳回提交到入网维护的完整的生命周期线下推广人员使用的客户端软件,但故事并没有这么简单... 疑问 随着app的逐渐完善,遇到的问题 ...
- 我的前端故事----关于前端数据&逻辑的思考
最近重构了一个项目,一个基于redux模型的react-native项目,目标是在混乱的代码中梳理出一个清晰的结构来,为了实现这个目标,首先需要对项目的结构做分层处理,将各个逻辑分离出来,这里我是基于 ...
- 我的前端故事----优美的编辑器GitHub Atom
很多前端的同学都在用sublime text,我之前也在使用,但是后来接触到了Atom,就被它的高颜值深深的吸引了~~不愧是GitHub的工程师哦~审美就是高 Atom 作为一个跨平台的编辑软件,安 ...
- 我的前端故事----Ajax方式和jsonp的实现区别
很久没有更新博客了,毕业2个月了,这段时间一直在忙于工作,一直没有时间更新,最近做的活动突然发现之前的经验居然忘记了...索性想想还是重新开始用博客记录平日里的工作经验吧,吐槽就到这里了,这篇记录的是 ...
- 我的前端故事----来聊聊怎么写react-native上的样式吧
我遇到了什么问题? 不久之前我重构了一个古老的项目,总结了一些js方面的想法,不过对于一个前端项目而言不仅仅只由js组成的嘛,上学的时候老师和我说HTML+CSS+JS对应的是页面的骨架.皮肤和肌肉. ...
- 我的前端故事----来聊聊react-native应用的健康监控
监控什么 今天我们来聊聊如何监控你的应用程序,这里的监控说的不是让我们去监控用户,而是监控应用的健康状态,什么是健康状态呢?对于后端的同学来说,在微服务的架构下,每个子服务是否正常工作.返回的结果是否 ...
- 我的前端故事----疯狂倒计时(requestAnimationFrame)
很久没有更新博客了...为了双十一准备了不少活动,终于结束了,有时间静静的坐下来总结一下了,在活动中最常用的就是倒计时了,晚上也有很多倒计时的例子了,那么今天带来的是一个新的方法和思路. 既然要介绍新 ...
- 实例讲解基于 React+Redux 的前端开发流程
原文地址:https://segmentfault.com/a/1190000005356568 前言:在当下的前端界,react 和 redux 发展得如火如荼,react 在 github 的 s ...
- Redux你的Angular 2应用--ngRx使用体验
Angular2和Rx的相关知识可以看我的Angular 2.0 从0到1系列第一节:Angular 2.0 从0到1 (一)第二节:Angular 2.0 从0到1 (二)第三节:Angular 2 ...
随机推荐
- 【Django】中间件
Middleware 这个地方把所有Request 拦截住,用我们自己的方式完成处理以后直接返回 Response.因此了解中间件的构成是非常必要的. Initializer: __init__(se ...
- 现代C++新四大名著及C++学习杂谈
现代C++新四大名著及C++学习杂谈 翻开自己的博客,在2012年8月我曾经写过如下一篇博客, <<C++学习的方法以及四大名著>> http://www.cnblogs.co ...
- ubuntu上安装nginx+mysql+php5-fpm(PHP5 - FastCGI Process Manager)
题外话:由于近段时间测试环境ssh链路质量不大好,经常短线.故我把整个安装过程放到screen里去执行,以防止断线中断了安装过程.执行screen -S install,这样断线后,只要再执行scre ...
- C#小爬虫,通过URL进行模拟发送接收数据
public async Task<string> SendDataAsync(HttpMethod httpMethod, string requestUrl, HttpContent ...
- thinkphp实现无限级分类
普通的方法(采用for和foreach) //显示 foreach($data as $k=>$v){ //str_repeat()函数用于把字符串重复指定的次数 $data[$k]['titl ...
- error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
一.情形描述 下载了一个VS的源码,不知道此源码的版本.使用VS2010编译时出现如下报错: error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏 二.解决方法 在VS2010界 ...
- ASP.NET windows验证IIS配置
Windows验证时,需要配置IIS,把匿名验证设为disable,windows验证设为enable,window7 默认为匿名验证为enable,windows验证为disable. 否则会sys ...
- 转:SpringMVC浅谈
因为项目文案需要,于是乎翻阅spring相关资料.顿觉该篇不错详尽易懂,特转载之. 转载出处: http://blog.csdn.net/gane_cheng/article/details/5278 ...
- 66. Plus One【leetcode】
Given a non-negative integer represented as a non-empty array of digits, plus one to the integer. Yo ...
- angular学习(二)-- Directive
1.2 指令:Directive AngularJS 通过被称为 指令 的新属性来扩展 HTML, 具体表现形式一般为带有前缀 ng-xxx 的 HTML 属性. 指令的使用形式 ng-xxx 的属性 ...