写这篇文章的主要目的是在如今的公司推荐使用 Vue,而在使用 Vue 的时候不少同事对为何要使用 Vuex 不理解,我自己是没有使用过 Vue 或者 Vuex 写过实际项目;有过一年左右的 React 和 Redux 相关技术的项目实践,主要是根据对等的一点经验所写;不当之处,欢迎讨论。html
组件化vue
组件通讯react
状态管理git
Vuex 是什么github
Vuex 有什么特色web
Vuex 解决了什么问题vuex
什么类型的数据适合放在 Vuex 管理redux
工具segmentfault
总结
参考
扩展阅读
Web Components提供了一种组件化的推荐方式,具体来讲,就是:
经过shadow DOM封装组件的内部结构
经过Custom Element对外提供组件的标签
经过Template Element定义组件的HTML模板
经过HTML imports控制组件的依赖加载
所谓组件化,核心意义莫过于提取真正有复用价值的东西。那怎样的东西有复用价值呢?
控件
基础逻辑功能
公共样式
稳定的业务逻辑
-- 摘自xufei的《2015前端组件化框架之路》
对组件的粒度进行细分,能够分为:
UI component: 纯 UI 组件,能够维护本地的 UI State,接收 props 做为数据渲染,保持纯函数形式,具备可复用性。
Logic component: 带有逻辑的 UI 组件,与数据打交道。
组件化这个词,在 UI 这一层一般指“标签化”,也就是把大块的业务界面,拆分红若干小块,而后进行组装。狭义的组件化通常是指标签化,也就是以自定义标签(自定义属性)为核心的机制。广义的组件化包括对数据逻辑层业务梳理,造成不一样层级的能力封装。
应用在组件化以后,组件之间必然存在某种联系;组件化意味着协同工做,一般存在着 父子组件
、兄弟组件
、跨级组件
等组件关系,那么组件之间如何进行协调工做,即组件通讯;
在 Vue 中,父子组件的关系能够总结为 props down
、events up
。
父子组件通讯:父组件经过 props
向下传递数据给子组件
子父组件通讯:子组件经过 events
给父组件发送消息
使用 $on(eventName)
监听事件
使用 $emit(eventName)
触发事件
非父子组件通讯:使用一个空的 Vue 实例做为中央事件总线
能够想象到在简单的 父子
,子父
组件之间的通讯是很轻松的,经过 props
和 events
便可实现;可是每每咱们的应用可能不仅有这么简单的层级关系,在多层跨级组件若是经过 props
去传递,那意味着一层一层的往子组件传递,最终你可能不知道当前组件的数据最终来自哪一个父组件(固然你能够逆着方向一层一层往上找),经过 events
事件机制显然也存在着相似的问题。若是你以为这样也能够接受,你可能不须要 Vuex;但若是你在想有没有什么好的模式优雅的去解决,你能够继续阅读下面的部分。
随着 JavaScript 单页应用开发日趋复杂,JavaScript 须要管理比任什么时候候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成还没有持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
管理不断变化的 state 很是困难。若是一个 model 的变化会引发另外一个 model 变化,那么当 view 变化时,就可能引发对应 model 以及另外一个 model 的变化,依次地,可能会引发另外一个 view 的变化。直至你搞不清楚到底发生了什么。state 在何时,因为什么缘由,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。
-- 摘自《Redux 中文文档》
在应用中,组件之间的通讯实际上是归根于应用的状态管理;而应用的状态是来自多方面的,如何对状态进行管理,提升代码的可维护性,提高开发效率;大多数主流框架对数据状态管理也都有了对应的方案:
React 专一于 UI 层,社区为其提供了 Redux、Mbox 等状态管理库
Vue 的团也提供了 Vuex 状态管理库
还有一些专门解决数据层的库,如 RxJS
回到本文的讨论点,这里咱们暂且只讨论 Vue;Vue 的核心库只关注视图层,单文件组件,其模板、逻辑和样式是内部耦合的,侧重数据和视图的同步;Vue 自己并无对数据状态的管理进行处理,但其提供了另一个相似 Redux 的解决方案 Vuex,一个集中式状态管理的库;也就是说,你可能不须要 Vuex,它只是对你应用状态进行管理的一个库。
Vuex 是一个专门为 Vue.js 应用所设计的集中式状态管理架构。
-- from vuex docs (updated in 2016-05-27)
...
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
-- from vuex docs (updated in 207-05-20)
上面的定义是摘自 Vuex 在 GitHub 上的文档,分别于 2016/05/27 和 2017/05/20 的更新记录;从中咱们能够梳理一下关键词:
集中式状态管理模式(注意是强调管理应用的全部组件的状态)
可预测(前提是以相应的规则做为保证)
先不着急看下面的内容,若是你看到以上两点特性,脑海已经有了答案,能很好的向你的同事解释清楚,前提是你的同事彻底没有任何 Vuex 的经验,那下面的内容你能够直接忽略。你也应该知道你的应用是否须要 Vuex 了。
从上面的定义中能够知道 Vuex 的特色其实就是下面两点:
集中式状态管理
可预测
在说集中式管理模式以前,咱们能够先来想一想常见的处理方式是怎样的,即每一个组件维护自身的数据和状态,自给自足,分而治之;其思路大体以下:
定义组件自身的初始数据
在组件内获取异步数据
根据数据渲染更新视图
// 渲染视图 <template> <h2>single file component</h2> <template> <script> export default { // 初始数据 data() { }, // 获取异步数据 created() { this.fetchData() }, methods { fetchData() { // do something } } } </script>
可简单对比 React 的思路:
// https://github.com/xufei/blog/issues/19#issuecomment-85989838 var render = function(Model) { return View; } event_loop(function(){ var currentView = render(currentModel); map_view_into_user_interface(currentView); })
分治带来的是可管理性,把组件设想成一个单一的东西,一个组件包含了自身须要的数据和视图,把查询逻辑封装在内部,外部只须要实现一个响应事件获取事件的东西基于能够了。即 Single File Component
概念,组件化后,整个应用的树结构能够一目了然,能够随意添加或者移除一个组件,而不会影响其余的组件,听起来很美好;但事情并不是那么完美,因为这种方式封装的组件的内部实现聚合了异步请求的数据和自身的状态,真正组装复用起来是存在必定问题的。好比:
在同一可视区域的冗余请求数
不一样层级组件的数据共享问题
...
集中式状态管理模式则以一个全局单例模式管理应用的状态,相似于全局对象,但不彻底同样。
Vuex 的状态管理存储是响应式的:就是当你的组件使用到了 Vuex 的某个状态,一旦它发生改变了,全部关联的组件都会自动更新相对应的数据。
不能直接修改 Vuex 的状态:修改 Vuex 的状态惟一途径是提交(commit) mutations 来实现修改
如上图,Vuex为Vue Components创建起了一个完整的生态圈,包括开发中的API调用一环。围绕这个生态圈,简要介绍一下各模块在核心流程中的主要功能:
Vue Components:Vue组件。HTML页面上,负责接收用户操做等交互行为,执行dispatch方法触发对应action进行回应。
dispatch:操做行为触发方法,是惟一能执行action的方法。
actions:操做行为处理模块。负责处理Vue Components接收到的全部交互行为。包含同步/异步操做,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操做就在这个模块中进行,包括触发其余action以及提交mutation的操做。该模块提供了Promise的封装,以支持action的链式触发。
commit:状态改变提交操做方法。对mutation进行提交,是惟一能执行mutation的方法。
mutations:状态改变操做方法。是Vuex修改state的惟一推荐方法,其余修改方式在严格模式下将会报错。该方法只能进行同步操做,且方法名只能全局惟一。操做之中会有一些hook暴露出来,以进行state的监控等。
state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局惟一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components经过该方法读取全局state对象。
Vue组件接收交互行为,调用dispatch方法触发action相关处理,若页面状态须要改变,则调用commit方法提交mutation修改state,经过getters获取到state新值,从新渲染Vue Components,界面随之更新
建议类比 Redux 的流程,大致上是相同的:
相对于分治(碎片化)的状态管理,多个状态分散的跨越在不一样组件交互在各个角落,每一个 View 会有相对应的 Model 维护状态;而集中式管理模式则用于将分散于组件的状进行集中化管理,提供一个全局的 store 存储管理应用的状态。集中式的状态管理可让总体的状态变化更加明晰,尤为是配合各自的 devtools。它具有如下特色:
components share state(组件之间共享状态)
state should be accessible from everywhere(全部状态能够方便获取)
components need to mutate the state(组件能够修改状态)
components need to mutate the state of another component(组件能够修改其余组件的状态)
上面提到集中式存储管理应用的全部组件的状态,而应用的状态上文已经有提到,这里大体能够分为:
UI 状态:用户输入的状态
数据状态:服务端传过来的数据状态
客户端信息:设备信息的状态
其余...
这里是有歧义的,集中式存储管理应用的全部状态,按照字面意思是将全部的状态都集中式管理,也就是存到 Vuex 的全局单一 store 中,显然咱们是不能这样去理解的,应该视应用场景而定的,大体也能够分为如下几种:
对于用户输入的状态,好比控制模态框的显示隐藏,咱们通常在组件内处理消化;对于须要须要跨组件通讯的,则能够存储在全局的 store 中,咱们能够将这一类状态称之为本地状态(local state)。
对于服务端传过来的数据状态,按照大多数的实践是存储在全局的 store 中,这样能够在任意的组件中均可以使用;固然,也能够只将多组件的共享的数据存储在全局的 store 中,单个组件须要的数据内部处理消化,组件销毁时对应的数据状态也会销毁。
对于客户端的信息或者一些其余的数据状态与上面两种方式在必定程度上也是类似的。这一切看起来并没什么问题,然而细细想一想,当一个应用的足够复杂时,咱们该如何去设计咱们的数据模型,本地共享的状态是存在 store 仍是经过事件机制去处理,服务端的数据是一股脑都塞给全局的 store 存在内存里仍是视应用场景而定,在 Vuex 的文档或者是 Redux 文档这都没有惟一的答案。假设服务端传过来的数据都存在 store, 那最终的 store 会有多大,这是一个值得探索的问题。那究竟什么样的数据适合存储在全局的 store 中?
另外,使用 Vuex 必须按照上述 Vuex 的工做流程去进行,定义对应的 actions
, mutations
等等,这显然是在强制约定你以相应的规则去编写你的应用,对大多数新人来讲,这是繁琐的。就至关于你获得了必定的好处,那你也得相应的有所付出。
至此,咱们大概讨论了因为组件化,会产生组件间相互通讯数据管理的问题,对此也有相应了的解决办法;然而,并无一种很好的方式告诉咱们到底什么类型的数据适合放在单一的 store 进行管理;回到 Vuex 的定义,将数据使用 Vuex 管理的主要缘由之一是解决组件间的数据共享。
所谓共享指的是,同一份数据被多处组件使用,而且要保持必定程度的同步:
故而,在开发应用时,如何设计抽象数据层,这个是没有惟一答案的。但若是明白了其间的利弊,好比独立了数据层,视图的职责就很是单一,无非就是根据订阅的数据渲染页面,视图组件间的通讯就会不多,你们都会去跟数据层交互,维护一份统一的数据结构。
到这里,你应该能够肯定你的应用是否应该使用 Vuex 了,若是你使用了 Vuex,那么它还有一些其余的附属产品;以下面的 vue-tool 调试工具,它可让你对你的应用状态了如指掌,保存状态快照,历史回滚/时光旅行等等特性。
合久必分,分久必合;Vue 提倡 Single File Component
概念将单一功能进行组件化封装,而 Vuex 的设计则是将分散在各处的状态进行合并集中管理的抽象模式。利弊在上面的文章也已说明,它是一种可选的方案,你用或者不用,取决于你的应用。
注:对组件化和状态管理方面主要参考徐飞的系列文章,严重推荐深度阅读徐飞关于 《web 应用》 和 《随笔系列》 文章