前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

Vuex

作者头像
ayqy贾杰
发布2019-06-12 12:29:07
1.2K0
发布2019-06-12 12:29:07
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬黯羽轻扬

一.出发点

在相对独立的组件中,action -> state -> view的单向数据流能得到保证。而真实业务场景经常需要状态传递及共享,一般方法是:

  • 状态传递:父子组件通信通过props完成(正向传属性值,反向传方法),对于兄弟组件间通信,则需要通过事件或者把状态提升到父级(把兄弟通信问题转换成父子通信)来完成
  • 状态共享:要么放在一个组件里,其它组件想办法拿到状态引用,要么提出来作为单例,供各组件共享

深层次的props传递比较难受,兄弟组件间的交错的事件通信会带来维护上的问题,提升状态到父级会让父级膨胀,管理过多细节状态。把共享状态放在一个组件里,其它组件取状态引用比较费劲,提出来作为单例稍好一些,但组件树外存在零散的共享状态,也可能会带来维护上的问题

把状态层单独提出来,能有效解决状态传递和共享的问题,再用action给状态变更添上语义,不仅缓解了维护上的问题,还带来了调试方面的好处

二.基本原则

  • 应用级的状态由store集中管理
  • 修改状态的唯一方式是commit同步的mutation
  • 异步逻辑放在action

认同便于管理的单一状态树、规范修改状态的方式,此外更贴近业务,从设计上考虑异步场景

三.结构

不像Redux一样奇怪(reducer乍看好像和Flux没什么关系),Vuex更像是中规中矩的Flux实现:

component 视图层 dispatch action
---
action 事件层 commit mutation
   异步 统一管理异步请求
---
mutation 响应层 mutate state
   同步 逻辑上原子级的状态修改
---
state 数据模型层 update model
   通过 数据绑定 映射到视图更新

其中,mutation, action都是全局共享的,所以也解决了组件通信的问题(不需要手动传递状态,只需要告诉store发生了什么,store知道该做什么),避免提升/传递状态,并带来了语义上的好处

全局共享就存在命名冲突的问题,所以Vuex还提供了命名空间选项

对比Flux

         产生action               传递action           update state
view交互 -----------> dispatcher -----------> stores --------------> views

可以发现最大的区别是Vuex把action细分成了actionmutation,分别应对异步场景和同步场景,由store自身充当dispatcher(负责注册/分发action/(mutation)

也就是说,把action, mutation看作一层(Flux里的action)的话,二者结构完全一致,所以说Vuex更像是中规中矩的Flux实现

store

作为state的容器,另外充当dispatcher

store来管理state,从作用上看相当于global.share = {},但Vuex里的store.state有一些别的特点:

  • state是响应式数据
  • 不允许直接修改store持有的state,必须显式的commit mutation

与组件的data类似,store.state也是响应式的,与组件的计算属性关联起来,state更新精确传递到view

而不允许直接修改store.state也是道德约束,虽然在开启strict选项后会报错,而实际上修改是可以生效的,这里不做强约束(写保护)可能是出于市场考虑

另外:

  • 单一状态树,与Redux相同,提供额外模块化机制来管理(拆分/组织state
  • 同样,不要求把所有state全都塞进Vuex,建议把相对独立的维护在组件级

getter

作用上相当于store的计算属性

用来包装state,把原始state包装(对store.state做简单计算,比如filter, count, find等等)成视图展示需要的形式

没有getter的话,这部分弱逻辑要么放在computed里,要么放在模版里,提供getterstate相关的所有东西都抽离出去

mutation

负责更新statemutation都是同步操作commit mutation下一行state就更新完了

预先注册在store中,每次commit时查mutation表,执行对应的state更新函数

注意,要求mutation必须是同步的,否则调试工具拿不到正确的状态快照(如果异步修改状态的话),会破坏状态追踪

action

用来应对异步场景,作为mutation的补充

Vuex相当于把Flux里的action按同步异步分为mutationaction

action不像mutation一样直接修改state,而是通过commit mutation来间接修改,也就是说只有mutation对应原子级的状态更新操作

action里可以有异步操作,设计上故意把异步作为action和同步的mutation分开

异步流程控制

异步流程控制可以通过让action返回promise来解决,比传入回调函数优雅一些

Vuex v2.x(目前2017/7/1最新v2.3.0)的store.dispatch默认返回promise,非promiseaction返回值会经Promise.resolve()包装成promise

dispatch(type: string, payload?: any, options?: Object) | dispatch(action: Object, options?: Object) Dispatch an action. options can have root: true that allows to dispatch root actions in namespaced modules. Returns a Promise that resolves all triggered action handlers.

(摘自API Reference)

对于异步操作没有意义Promise.resolve(undefined)),需要控制异步流程的话,还是应该手动返回promise,并把需要的信息从内层promise传递出来

module

模块化机制,用来拆分组织store

提供namespaced选项,注册时把模块路径作为前缀。很精致的设计,通过向模块注入local.dispatch/commit/getters/state来抹平命名空间的影响,模块内不用带命名空间,模块外(业务或者其它模块)需要带命名空间。这样命名空间就变成了一个开关选项,对store部分没有任何影响

四.工具

同样,Vuex也需要处理state -> view的部分(作用类似于react-redux,把状态管理层接入视图层)

支持精确数据绑定的Vue不用像React那么麻烦(往虚拟DOM树上插一些container,把store.state的变化传播下去),只需要把store.state和组件状态连接起来就行,像软链接一样,组件与store共享state对象,state的变化通过响应式特性传递给组件,视图得到更新

mapState

store.state和组件的computed连接起来

注意:mapState能够强制禁止在组件里直接修改computed影响实际状态(通过mapState生成的计算属性是只读的)

{
   configurable: true,
   enumerable: true,
   get: function computedGetter(),
   set: function noop()
}

mapGetters

store.getter和组件的computed连接起来

mapState类似,也有写保护

mapMutations

mutation和组件的methods连接起来

简化组件commit mutation的过程(需要在顶层注入store

mapActions

action和组件的methods连接起来

简化dispatch action的过程(同样需要注入store

五.疑问

1.怎样避免相同组件共享状态?

比如list里有3个相同组件,怎样避免共享state带来的状态一致问题?

模块复用与状态共享的冲突。像处理data一样,用函数state返回新状态对象,而不用对象state。这样3个组件对应的statestore.state上的一小块)都是独立的,而且不需要额外的状态管理

注意,函数state的特性在Vue v2.3.0+可用,低版本需要考虑别的方式,比如:

  • state提升一级(维护一个数组,管理state list
  • 考虑把无法共享的局部状态放到组件级,把可共享的数据及操作放到store

第一种方式会让store迅速膨胀,而且action/mutation等等都需要index,组件需要把index传回给store,太麻烦不可取

第二种方式是终极解决办法,划分state的技巧在哪里都适用,不要单纯为了Vuex化而使用Vuex。把所有状态都从组件抽离出来放在store里也不是不可以,但store持有的状态过于细致的话,对开发维护来说都是巨大的麻烦:

  • 开发时组件里的任何一个细微变化,都要走dispatch/commit
  • 维护时会面对一个非常复杂的store,上千个mutation type

而这麻烦完全是自找的。那么考虑状态该如何划分:

  • 交互相关的UI状态,放在组件级。比如展开/收起、loading显示/隐藏、tab/表格分页等等
  • 无法共享的数据状态,放在组件级。比如表单输入数据
  • 可共享的数据状态,放在状态层。比如可缓存的服务数据

store的角色应该是server + database,作为前端数据层存在,而不是单纯地把应用状态从组件树抽离出来作为状态树,没有太大意义

2.computed属性和vuex的store.state怎么关联起来的?

运行时依赖收集机制

// 组件
computed: {
   user() {
       return this.$store.state.user;
   }
}// store
mutations: {
   [types.SET_USER] (state, user) {
       state.user = user;
   }
}

计算各computed属性,执行user()过程中访问了store.state.user,触发stategetter,把user()函数依赖store.state.user这个信息记录下来

之后commit mutation修改了store.state.user的话,触发statesetter,对user属性对应的所有依赖项(其中有user()函数)重新求值

接着触发computedsetter,执行computed.user对应的所有依赖项(其中有视图更新函数),视图更新完成

P.S.依赖收集机制的具体实现见vue/src/core/observer/dep.js

3.store传递机制

与react-redux的Provider类似,也提供了一次注入全局可用的方式(Vue.use(Vuex)并在new顶层组件时传入store

Vuex作为插件,通过修改Vue.prototype,把$store挂上去,让所有vm共享

4.input等双向绑定场景与store.state不能直接修改的冲突

通过计算属性的getter/setter来处理:

  • getter里读store.state
  • settercommit mutationstore.state

参考资料

  • Vuex
  • Creating multiple instances of Vuex modules
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-07-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.出发点
  • 二.基本原则
  • 三.结构
    • 对比Flux
      • store
        • getter
          • mutation
            • action
              • 异步流程控制
            • module
            • 四.工具
              • mapState
                • mapGetters
                  • mapMutations
                    • mapActions
                    • 五.疑问
                      • 1.怎样避免相同组件共享状态?
                        • 2.computed属性和vuex的store.state怎么关联起来的?
                          • 3.store传递机制
                            • 4.input等双向绑定场景与store.state不能直接修改的冲突
                              • 参考资料
                              相关产品与服务
                              容器服务
                              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档