从计数器开始

让我们从一个简单的计数器,开始进入Vuex 的世界:

vuex 计数器 汇智网

计数器应用的数据模型很简单:使用一个counter属性来表示计数器的 当前值就够了。

Vue实例的created钩子 中,应用启动了一个定时器,用来周期性地 递增counter属性的值 —— 由于counter是响应式属性,它的变化因而 驱动了视图随之刷新。

可以说counter抽象地表达了计数器视图的本质特征,当counter的 值确定时,我们可以确定地推理出视图的表现。像counter这样可以决定 视图表现的数据,在Vuex中就被称为状态

计数器应用相当简单,因此我们只需要定义一个状态就可以了。稍微复杂 一些的应用,则可能需要我们抽象出成百上千的状态,这时候就需要分类 管理了。

例如,对于一个电商应用,我们将其购物车相关的状态归入cart类:

vuex 购物车 汇智网

你看到,应用的全部状态,构成了一棵层级分明的状态树。而Vuex的作用 ,就在于管理一个应用的状态树。

应用单一状态树

Vuex进行应用状态管理 的第一个手段,是要求应用建立并维护一个单一的、全 应用范围共享的状态树,而不是各个组件单独维护自己的状态(在组件中使用data配置项声明) —— 不过这不是绝对的,那些完全不需要在多个组件间 共享的状态,依然可以在组件内部声明。

出于学习的目的,同时为了避免引入复杂的演示代码,我们假设计数器的counter状态需要与其他组件共享,因此我们将其定义迁移到状态树中。

创建状态库

Vuex的Store类 —— 状态库 —— 用于管理状态树,它的实例化配置项state用来声明要创建的状态树。例如,下面的代码创建了一个包含状态counter的 状态库:

conststore =newVuex.Store(

{

            state:{ counter:0}

       }

)

利用状态库的state属性,就可以访问到其管理的状态树了。例如,通过store.state.counter来访问counter状态。

需要指出的是,状态库的state属性 —— 状态树 —— 是一个响应式属性,因此 我们可以使用状态树上的这些状态来驱动视图的自动更新。

使用计算属性访问状态树

在建立了全应用单一状态树之后,接下来我们要考虑的就是在组件中怎么使用 树上的状态了 —— 我们已经决定不声明组件的私有状态。

最简单的方法是将树上的状态,映射为组件的计算属性。例如,下面的代码将 状态树上的counter状态,映射为组件的可读写计算属性:

constEzCounter = { 

    template:'{ {counter} }',  

    computed:{    

        counter:{

            get() {returnstore.state.counter },

            set(v){ store.state.counter = v }    

        }

    }

}

将状态库注入组件

另一种方法是将状态库挂接为Vue实例的一个属性上,这样我们就可以在模板中 直接访问状态树了(模板的上下文对象是所属的Vue实例)。

在创建Vue实例时,使用store配置项,就可以将状态库挂接为Vue实例 的属性$store,而且这个Vue实例的所有后代实例,也都有$store指向 同一个状态库,看起来就像是将状态库注入了组件树上的每一个实例。

例如,下面的代码使用store配置项,将状态库store注入根组件,因此 我们可以在EzCounter组件中利用$store属性访问状态库:

const EzCounter = { 

    template:'{{$store.state.counter}}'

}

new Vue({  
      store:store,  

      template:'',  

      components:{EzCounter}

})

状态变更管理

Vuex进行状态管理 的第二个手段,是承担起管理状态变更(mutation)的 责任。

Vuex要求组件将状态树视为只读,组件不应该直接修改状态树上的状态, 而是通过提申请的方式,由状态库来实际执行状态变更的操作:

vuex 状态 汇智网

对于计数器应用来讲,修改counter状态的需求有两个:递增和复位。 因此,我们需要首先在状态库中声明两个变更处理器(mutation handler)。

在创建状态库时,使用mutations配置项来声明变更处理器。例如,下面 的代码为状态库声明了两个变更处理器:INCREASE和RESET:

conststore =newVuex.Store({

        state:{counter:0},

        mutations:{
                          INCREASE:state=>state.counter++ ,
                          RESET:state=>state.counter =0
                        }
})

Vuex推荐使用大写字母来命名变更处理器,因为这个名字也将作为 组件提交的变更请求的类型名 —— 从组件的角度看,还有比大写的名字 更能表达出这是一个请求而不是实际操作吗 —— 回忆一下Windows 的WM_系列消息的名字。

状态变更处理器(mutation handler)的参数是一个局部上下文 (local context)对象的state属性,我们需要利用这个参数来 更新状态树上指定的状态。局部上下文是Vuex为实现状态树的模块化管 理而构造的状态库局部镜像,我们将在《模块化管理》章节详细讲解 局部上下文(local context)对象。现在,就把它暂时理解为原始的 状态库好了。

提交变更请求

组件应当调用状态库的commit()方法来提交指定类型的状态变更请求。 例如,下面的代码向状态库提交了递增counter状态的申请:

store.commit('INCREASE')

状态变更的同步性

Vuex进行状态管理 的第三个手段,是要求应用保证状态变更(mutation)的 同步性 —— 状态变更处理器执行完之时,状态更新一定要完成。

这意味着,在状态变更处理器里不能执行异步代码。这一要求直接导致了新的 环节的引入 —— 状态动作(action):

image

根据作者的说法(原文参见:About mutations usefulness), 引入状态动作(action)这一环节的唯一目的,就是为了保证状态变更(mutation) 的同步性:"Actions vs. mutations is all about separating asynchronicity from actual mutations"

状态动作(action)隔离了组件和状态库,组件现在应当分发执行状态库声明的动作, 然后由状态动作负责提交变更请求。

在创建状态库时,使用actions配置项声明状态动作。例如,下面的代码 声明了两个状态动作inc和reset,分别用来提交INCREASE和RESET变更 请求:

conststore =newVuex.Store({

    state:{counter:0},

    mutations:{INCREASE:state=>state.counter++,RESET:state=>state.counter =0},

    actions:{inc:context=>context.commit('INCREASE'),

    reset:context=>context.commit('RESET')  }

})

状态动作(action handler)的参数是一个局部上下文(local context)对象, 我们已经知道它是Vuex为模块构造的状态库局部镜像,因此调用它的commit()方法, 就可以提交变更请求了。

分发执行状态动作

调用状态库的dispatch()方法来分发执行指定名称的状态动作。例如, 下面的代码要求状态库执行inc动作:

store.dispatch('inc')

更多关于vuex的精彩内容请到这里来:

最新的 Vuex 2 入门与提高教程