flux架构浅谈:什么数据才应该放store
相信很多用过react和vue等框架的人都有思考过这个问题。React的出现,随之带来了flux这一设计理念。从React的Reflux、Redux,再到vue的vuex,一路使用过来,我觉得是该总结一下的时候了。
flux的机制已经有太多文章写过了,这里就不再多说。如果还不了解的,可以参看阮一峰的Flux 架构入门教程。这里简单介绍一下目前主流的三种flux架构:
Reflux是我最早接触的flux架构,它把flux的结构简化了,带来了一些便利,但却做得不够好。它的store并不唯一,并且允许你建立多个store。如果处理不好,维护起来也是够呛。
Redux在这方面就做得很棒,它的store只有一个,并且引入了immutable的概念,使得store的可预测性更进一步。但是它的异步操作是痛点,虽然有如saga这种优秀的中间件,但对于新手来说,还是很容易绕晕。
vuex这方面做得挺不错,它把异步操作限定在了actions阶段,减少了很多繁琐步骤。它的store也是唯一,但你可以在任何地方用this.$store去引用,方便的同时,对习惯不好的同学,也会带来一些隐患。
好了,接下来我们该进入主题了。
多方观点
前两天,和同事在store里面应该存放何种数据的问题上产生了分歧,于是我查了不少资料,也跟一些技术大牛进行过探讨。总结起来,目前主流的观点有两种:
store只存放公共数据,组件的数据、状态自行维护
所有数据都应走store,view层只管展现数据,这也是flux官方所推荐的做法
这里特别强调一下,本文所探讨的是SPA应用。所以,如无特殊说明,这里指的组件,特指页面级组件,或者说路由级组件,并非单组件。另外,flux官方并无数据的概念,所有数据均是状态。那我为什么要特意抽出来呢?后面会说。
持观点1的人认为:只有公共的数据、需要跨组件的数据才应放入store。优点是store小,页面组件内的数据不必绕来绕去,开发简单。
持观点2的人认为:呃,貌似我自己也差不多这种思想,为免带入个人思想,这里就不妄自猜度他人想法了。
store定位
目前各方都有一定的支持人数,那么,要理清这个问题,我们就得先把store的角色定位好。是做为数据中心呢?还是做为全局变量仓库?或者还有另外的想法?
自从接触flux以来,我也一直遵循第二种思路,但又不完全是。store是什么?从字面上理解,就是一份存储,一份放在本地的存储。再来看一下我们目前的前后端交互,拉取 => 展现,再拉取 => 再展现。每切换一次页面,都要再拉取一次数据。我们为什么不能做到更好呢?把store定位为一份服务器数据本地快照如何?没错,这就是我的思路。
传统应用开发,我们的应用程序都是直接和数据库交互的。但前端不行,抛开还需要后端程序提供接口不说,前端与数据库中间还隔着http这层,这就产生了巨大的延时。所以,为什么我们不能在本地建立一份数据快照?用store来做这事是不是合适?
也许你会说,localStorage、indexDB来做这事才更合适。localStorage大小且不说,你还需要借助JSON来进行数据维护。而indexDB,操作起来也并不简便。并且,他们的存取速度与store也明显不是一个级别(虽然用户几乎感觉不到差异)。
方案
除类似autocomplate这类的sideways数据,所有从后端拉取的数据,以页面级组件为单位,都存入store,做为服务器数据本地快照。这样,当用户切换页面时,只有view会被销毁。每次用户进入该页面,首先会拿到一份快照数据,store同时向服务器查询最新数据。如有更新,可做提示有新数据(如新浪微博)。
而对于状态(对,前面说了我把数据和状态分别开来对待。数据指业务数据,而状态,我理解为行为状态),除非是全局状态,否则,管你是页面级组件还是单组件,自个儿内部维护。
特别说明,store里的数据,应当尽量保持是原始的服务器数据快照,而不应该去做任何mutate,类似Redux的immutable思想。而对于add、del之类操作的结果,也不应当放入store,而只是一种状态反馈,应当在组件内部消化。
这样,我们的store里就只有纯粹的两种东西:全局状态和业务数据(服务器数据快照)。
更进一步,我们还可以借助本地存储把store做为本地缓存,达到重启app可立即恢复现场的效果。
优点
用户体验,对于切换频率高的页面,用户体验直线上升。设想一下,如果数据放到页面组件内部,离开当前页数据即被销毁,当用户一秒后再次返回该页。。。
资源优化,减少对服务器的请求,对于更新周期长的数据,例如用户个人信息,完全可以仅请求一次
可预测性,flux所提倡的数据可预测成为可能,各种数据追踪工具得以大展身手(微信小程序开发工具的数据追踪就挺不错)
可读性,component只有view逻辑,不掺杂modal逻辑,业务逻辑清晰易维护
扩展性强,例如需要加入权限控制,进行数据过滤,在store层面就可以轻松解决。而如果业务数据在组件内部。。。
统一性,所有业务数据统一存取,管理方便。
缺点
再来看看持观点1的人所反对的:
store庞大,持观点1的人首先反对的就是这个。但是,对于SPA来说,对于现代PC来说,你的SPA的store能达到以G为单位不?
编写繁琐,嗯,这算一个理由吧。不过,对于其所带来的优点来说,这点牺牲并不算什么吧?
数据时效,有人提到,一次性数据(比如订单结算)不应被放入store。没错,这点我们可以在页面destory时手动清理不是么?
单store文件难维护,有人提到,所有业务数据操作都在store里,难维护。其实利用现在的构建工具,我们已经可以做到把各个页面组件的store单独切出来,构建时再合并,这并不会影响可维护性。
数据污染,有人担心,这么多数据放到store里,会不会造成污染。对于这点,无论Redux和vuex,都是支持子store的。也就是命名空间的概念。比如 store.pageModule1、store.pageModule2。再配合一些测试工具(如mocha)做重名检测,这方面完全不是问题
还有什么
实际开发过程中,还有一个经常遇到的,经常被人问起的问题:公共页面组件容易加载到非自身数据。比如文章详情页,如果先浏览了文章1,再浏览文章2,有可能先显示文章1的内容,会很怪异。其实这也是一个很容易解决的问题,进入一个页面组件时,首先加载的其实是initialState,紧接着,flux才把store里的数据推过来。这个时候,你应该做文章id校验,只有是自身数据,才assign。
适用场景
记得有人说过这样的话:“如果你不知道该不该用flux,那说明你并不需要它。” Redux的作者 Dan Abramov 说:“Flux 架构就像眼镜:您自会知道什么时候需要它。” 如果你的业务只是需要一个全局变量仓库,flux会不会过重?而对于大多数SPA场景,我想这个思路都是行得通的。另外,对于一次性数据比较多的应用,那这种思路或许也不太适合。
响应式store设想
我觉得最理想的状态,flux架构还可以更进一步,把store做得更纯粹一些,可以自行维护数据。具体做法是,ajax放到store层面,当请求的数据不存在或过旧时,自动拉取,而不需要通过actions来处理拉取数据的逻辑。