Vue 入门之 Vuex 实战

Vue 入门之 Vuex 实战

引言

Vue 组件化作的确实很是完全,它独有的 vue 单文件组件也是作的很是有特点。组件化的同时带来的是:组件之间的数据共享和通讯的难题。 尤为 Vue 组件设计的就是,父组件经过子组件的 prop 进行传递数据,并且数据传递是单向的。也就是说:父组件能够把数据传递给子组件,可是 反之则不一样。以下图所示:css

vue父子传递

单向数据流动

单方向的数据流动带来了很是简洁和清晰的数据流,纯展现性或者独立性较强的模块的开发确实很是方便和省事。 可是复杂的页面逻辑,组件之间的数据共享处理就会须要经过事件总线的方式解决或者使用 Vue 的 Vuex 框架了。html

子组件通知父组件数据更新:事件方式的实现

子组件能够在子组件内触发事件,而后在父容器中添加子组件时绑定父容器的方法为事件响应方法的方式.以下图所示:vue

vue父子传递

  • 使用 v-on 绑定自定义事件
每一个 Vue 实例都实现了事件接口(Events interface),即:
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件

参考代码案例:jquery

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Vue入门之event message</title>
  <!-- 新 Bootstrap 核心 CSS 文件 -->
  <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">

  <!-- 可选的Bootstrap主题文件(通常不用引入) -->
  <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap-theme.min.css">

  <!-- jQuery文件。务必在bootstrap.min.js 以前引入 -->
  <script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>

  <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
  <script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>

  <script src="https://unpkg.com/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>

<body>
  <div id="app">
    <p>推荐次数:{{ voteCount }}</p>
    <hr>
    <!--绑定两个自定义事件,当组件内部触发了事件后,会自定调用父容器绑定的methods的方法,达到了子容器向父容器数据进行通讯同步的方法-->
    <vote-btn v-on:vote="voteAction" v-on:sendmsg="sendMsgAction"></vote-btn>
    <hr>
    <ul class="list-group">
      <li v-for="o in msg" class="list-group-item">{{o}}</li>
    </ul>
  </div>
  <script>
    Vue.component('vote-btn', {
      template: `
        <div>
          <button class="btn btn-success" v-on:click="voteArticle">推荐</button>
          <hr/>
          <input type="text" v-model="txtMsg" />
          <button v-on:click="sendMsg" class="btn btn-success">发送消息</button>
        </div>
      `,
      data: function () {
        return {
          txtMsg: ""
        }
      },
      methods: {
        voteArticle: function () {
          // 触发事件,vote
          this.$emit('vote')
        },
        sendMsg: function () {
          // 触发事件,sendmsg,并
          this.$emit('sendmsg', this.txtMsg)
        }
      }
    })

    var app = new Vue({
      el: '#app',
      data: {
        voteCount: 0,
        msg: []
      },
      methods: {
        voteAction: function() {  // 事件触发后,会直接执行此方法
          this.voteCount += 1
        },
        sendMsgAction: function (item) {
          this.msg.push(item)
        }
      }
    });
  </script>
</body>

</html>

 

事件总线方式解决非父子组件数据同步

若是非父子组件怎么经过事件进行同步数据,或者同步消息呢?Vue 中的事件触发和监听都是跟一个具体的 Vue 实例挂钩。 因此在不一样的 Vue 实例中想进行事件的统一跟踪和触发,那就须要一个公共的 Vue 实例,这个实例就是公共的事件对象。git

参考下面作的一个购物车的案例的代码:github

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Vue入门之event message</title>
  <!-- 新 Bootstrap 核心 CSS 文件 -->
  <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">

  <!-- 可选的Bootstrap主题文件(通常不用引入) -->
  <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap-theme.min.css">

  <!-- jQuery文件。务必在bootstrap.min.js 以前引入 -->
  <script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>

  <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
  <script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>

  <script src="https://unpkg.com/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>

<body>
  <div id="app">
    <product-list :products="products" v-on:addpro="addToCarts"> </product-list>
    <hr>
    <cart :cart-products="carts"> </cart>
  </div>
  <script>
    var eventBus = new Vue();
    Vue.component('cart', {
      template: `
      <table class="table table-borderd table-striped table-hover">
      <thead>
        <tr>
          <th>商品编号</th>
          <th>商品名</th>
          <th>数量</th>
          <th>操做</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in cartProducts">
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>
            {{ item.count }}
          </td>
          <td>
            <button type="button" @click="removeCarts(item)" class="btn btn-success"><i class="glyphicon glyphicon-remove"></i></button>
          </td>
        </tr>
      </tbody>
    </table>
      `,
      data: function () {
        return {
        }
      },
      methods: {
        removeCarts: function (item) {
          eventBus.$emit('remo', item)
        }
      },
      props: ['cartProducts']
    })

    Vue.component('product-list', {
      template: `
      <table class="table table-borderd table-striped table-hover">
      <thead>
        <tr>
          <th>商品编号</th>
          <th>商品名</th>
          <th>操做</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in products">
          <td>{{ item.id }}</td>
          <td>{{ item.name }}</td>
          <td>
            <button type="button" v-on:click="addToCarts(item)" class="btn btn-success"><i class="glyphicon glyphicon-shopping-cart"></i></button>
          </td>
        </tr>
      </tbody>
    </table>
      `,
      data: function () {
        return {
        }
      },
      methods: {
        addToCarts: function (item) {
          this.$emit('addpro', item)
        }
      },
      props: ['products'],
    })

    var app = new Vue({
      el: '#app',
      data: {
        products: [
          { id: '1', name: '鳄鱼' },
          { id: '2', name: '' },
          { id: '3', name: '兔子' },
          { id: '4', name: '' },
          { id: '5', name: '孔雀' }
        ],
        carts: []
      },
      methods: {
        addToCarts: function (item) {
          var isExist = false
          for(var i=0; i<this.carts.length; i++) {
            if( item.id === this.carts[i].id ) {
              item.count = this.carts[i].count + 1
              Vue.set(this.carts, i, item)
              isExist = true
            }
          }
          !isExist && (item.count = 1, this.carts.push(item))
        },
        removeCarts: function (item) {
          for(var i =0; i<this.carts.length; i++) {
            if( item.id === this.carts[i].id) {
              this.carts.splice(i,1)
            }
          }
        }
      },
      mounted: function () {
        self = this;
        eventBus.$on('remo', function (item) {
          self.removeCarts(item)
        })
      }
    });
  </script>
</body>
</html>

 

Vuex 解决复杂单页面应用

上面的方式只能解决一些简单的页面中的组件的通讯问题,可是若是是复杂的单页面应用就须要使用更强大的 Vuex 来帮咱们进行状态的统一管理和同步。vue-router

当第一次接触 Vuex 的时候,眼前一亮,以前通过 Redux 以后,被它繁琐的使用令我痛苦不已,虽然思路很清晰,其实彻底能够设计的更简单和高效。 当我接触到 Vuex 以后,发现这就是我想要的。的确简洁就是一种艺术。vuex

其实本质上,Vuex 就是一个大的 EventBus 对象的升级版本,至关于一个特定的仓库,全部数据都在统一的仓库中,进行统一的管理。bootstrap

几个核心的概念:api

  • StateVuex 仓库中的数据。
  • Getter: 相似于 Vue 实例中的计算属性,Getter 就是普通的获取 state 包装函数。
  • Mutations: Vuex 的 store 中的状态的惟一方法是提交 mutation。Vuex 中的 mutations 很是相似于事件:每一个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。
  • Action: action 能够触发 Mutations,不能直接改变 state。

看下面一张图了解一下 Vuex 总体的数据流动:

再看一张具体点的:

vuex

建立Vuex实例 Vuex.Store

咱们能够经过Vuex提供的Vuex.Store构造器来构造一个Vuexstore实例。

import Vuex from 'vuex'

const store = new Vuex.Store({ ...options })

 

如下是关于Vuex.Store 构造器选项的说明。

state

  • 类型: Object | Function

    Vuex store 实例的根 state 对象。

    若是你传入返回一个对象的函数,其返回的对象会被用做根 state。这在你想要重用 state 对象,尤为是对于重用 module 来讲很是有用。

单一状态树

Vuex 使用单一状态树——是的,用一个对象就包含了所有的应用层级状态。至此它便做为一个“惟一数据源 (SSOT)”而存在。这也意味着,每一个应用将仅仅包含一个 store 实例。单一状态树让咱们可以直接地定位任一特定的状态片断,在调试的过程当中也能轻易地取得整个当前应用状态的快照。

单状态树和模块化并不冲突——在后面的章节里咱们会讨论如何将状态和状态变动事件分布到各个子模块中。

在 Vue 组件中得到 Vuex 状态

那么咱们如何在 Vue 组件中展现状态呢?因为 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:

// 建立一个 Counter 组件
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

 

每当 store.state.count 变化的时候, 都会从新求取计算属性,而且触发更新相关联的 DOM。

然而,这种模式致使组件依赖全局状态单例。在模块化的构建系统中,在每一个须要使用 state 的组件中须要频繁地导入,而且在测试组件时须要模拟状态。

Vuex 经过 store 选项,提供了一种机制将状态从根组件“注入”到每个子组件中(需调用 Vue.use(Vuex)):

const app = new Vue({
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这能够把 store 的实例注入全部的子组件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

 

经过在根实例中注册 store 选项,该 store 实例会注入到根组件下的全部子组件中,且子组件能经过 this.$store 访问到。让咱们更新下 Counter 的实现:

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

 

mapState 辅助函数

当一个组件须要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,咱们可使用 mapState 辅助函数帮助咱们生成计算属性,让你少按几回键:

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可以使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了可以使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

 

当映射的计算属性的名称与 state 的子节点名称相同时,咱们也能够给 mapState 传一个字符串数组。

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

 

对象展开运算符

mapState 函数返回的是一个对象。咱们如何将它与局部计算属性混合使用呢?一般,咱们须要使用一个工具函数将多个对象合并为一个,以使咱们能够将最终对象传给 computed 属性。可是自从有了对象展开运算符(现处于 ECMASCript 提案 stage-4 阶段),咱们能够极大地简化写法:

computed: {
  localComputed () { /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })
}

 

组件仍然保有局部状态

使用 Vuex 并不意味着你须要将全部的状态放入 Vuex。虽然将全部的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。若是有些状态严格属于单个组件,最好仍是做为组件的局部状态。你应该根据你的应用开发须要进行权衡和肯定。

mutations

  • 类型: { [type: string]: Function }

    在 store 上注册 mutation,处理函数老是接受 state 做为第一个参数(若是定义在模块中,则为模块的局部状态),payload 做为第二个参数(可选)。 更改 Vuex 的 store 中的状态的惟一方法是提交 mutation。Vuex 中的 mutation 很是相似于事件:每一个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是咱们实际进行状态更改的地方,而且它会接受 state 做为第一个参数:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变动状态
      state.count++
    }
  }
})

 

你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你须要以相应的 type 调用 store.commit 方法:

store.commit('increment')

 

提交载荷(Payload)

你能够向 store.commit 传入额外的参数,即 mutation 的 载荷(payload):

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}

 

store.commit('increment', 10)

 

在大多数状况下,载荷应该是一个对象,这样能够包含多个字段而且记录的 mutation 会更易读:

// ...
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

 

store.commit('increment', {
  amount: 10
})

 

对象风格的提交方式

提交 mutation 的另外一种方式是直接使用包含 type 属性的对象:

store.commit({
  type: 'increment',
  amount: 10
})

 

当使用对象风格的提交方式,整个对象都做为载荷传给 mutation 函数,所以 handler 保持不变:

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

 

Mutation 需遵照 Vue 的响应规则

既然 Vuex 的 store 中的状态是响应式的,那么当咱们变动状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也须要与使用 Vue 同样遵照一些注意事项:

  1. 最好提早在你的 store 中初始化好全部所需属性。

  2. 当须要在对象上添加新属性时,你应该

    • 使用 Vue.set(obj, 'newProp', 123), 或者

    • 以新对象替换老对象。例如,利用 stage-3 的对象展开运算符咱们能够这样写:

      state.obj = { ...state.obj, newProp: 123 }

       

使用常量替代 Mutation 事件类型

使用常量替代 mutation 事件类型在各类 Flux 实现中是很常见的模式。这样可使 linter 之类的工具发挥做用,同时把这些常量放在单独的文件中可让你的代码合做者对整个 app 包含的 mutation 一目了然:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

 

// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 咱们可使用 ES2015 风格的计算属性命名功能来使用一个常量做为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

 

用不用常量取决于你——在须要多人协做的大型项目中,这会颇有帮助。但若是你不喜欢,你彻底能够不这样作。

Mutation 必须是同步函数

一条重要的原则就是要记住 mutation 必须是同步函数。为何?请参考下面的例子:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

 

如今想象,咱们正在 debug 一个 app 而且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都须要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:由于当 mutation 触发的时候,回调函数尚未被调用,devtools 不知道何时回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

在组件中提交 Mutation

你能够在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(须要在根节点注入 store)。

 
import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}
下一步
 

 

:Action

在 mutation 中混合异步调用会致使你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道何时回调和哪一个先回调呢?这就是为何咱们要区分这两个概念。在 Vuex 中,mutation 都是同步事务:

store.commit('increment')
// 任何由 "increment" 致使的状态变动都应该在此刻完成。

 

actions

  • 类型: { [type: string]: Function }

    在 store 上注册 action。处理函数老是接受 context 做为第一个参数,payload 做为第二个参数(可选)。

    context 对象包含如下属性:

    { state, // 等同于 `store.state`,若在模块中则为局部状态 rootState, // 等同于 `store.state`,只存在于模块中 commit, // 等同于 `store.commit` dispatch, // 等同于 `store.dispatch` getters, // 等同于 `store.getters` rootGetters // 等同于 `store.getters`,只存在于模块中 }

    同时若是有第二个参数 payload 的话也可以接收。

    详细介绍

getters

  • 类型: { [key: string]: Function }

在 store 上注册 getter,getter 方法接受如下参数:

state,     // 若是在模块中定义则为模块的局部状态
  getters,   // 等同于 store.getters

当定义在一个模块里时会特别一些:

state,       // 若是在模块中定义则为模块的局部状态
  getters,     // 等同于 store.getters
  rootState    // 等同于 store.state
  rootGetters  // 全部 getters

注册的 getter 暴露为 store.getters

Getter 接受 state 做为其第一个参数:

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

 

mapGetters 辅助函数

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

 

若是你想将一个 getter 属性另取一个名字,使用对象形式:

mapGetters({
  // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})

 

modules

  • 类型: Object

    包含了子模块的对象,会被合并到 store,大概长这样:

    { key: { state, namespaced?, mutations, actions?, getters?, modules? }, ... }

    与根模块的选项同样,每一个模块也包含 state 和 mutations 选项。模块的状态使用 key 关联到 store 的根状态。模块的 mutation 和 getter 只会接收 module 的局部状态做为第一个参数,而不是根状态,而且模块 action 的 context.state 一样指向局部状态。

strict

  • 类型: Boolean
  • 默认值: false

    使 Vuex store 进入严格模式,在严格模式下,任何 mutation 处理函数之外修改 Vuex state 都会抛出错误。

    详细介绍

Vuex.Store 实例属性

state

  • 类型: Object

    根状态,只读。

getters

  • 类型: Object

    暴露出注册的 getter,只读。

Vuex.Store 实例方法

commit

  • commit(type: string, payload?: any, options?: Object)
  • commit(mutation: Object, options?: Object)

    提交 mutation。options 里能够有 root: true,它容许在命名空间模块里提交根的 mutation。详细介绍

dispatch

  • dispatch(type: string, payload?: any, options?: Object)
  • dispatch(action: Object, options?: Object)

    分发 action。options 里能够有 root: true,它容许在命名空间模块里分发根的 action。返回一个解析全部被触发的 action 处理器的 Promise。详细介绍

replaceState

  • replaceState(state: Object)

    替换 store 的根状态,仅用状态合并或时光旅行调试。

watch

  • watch(fn: Function, callback: Function, options?: Object): Function

    响应式地侦听 fn 的返回值,当值改变时调用回调函数。fn 接收 store 的 state 做为第一个参数,其 getter 做为第二个参数。最后接收一个可选的对象参数表示 Vue 的 vm.$watch 方法的参数。

    要中止侦听,调用此方法返回的函数便可中止侦听。

subscribe

  • subscribe(handler: Function): Function

    订阅 store 的 mutation。handler 会在每一个 mutation 完成后调用,接收 mutation 和通过 mutation 后的状态做为参数:

    store.subscribe((mutation, state) => {
      console.log(mutation.type)
      console.log(mutation.payload)
    })

     

    要中止订阅,调用此方法返回的函数便可中止订阅。

    一般用于插件。详细介绍

subscribeAction

  • subscribeAction(handler: Function): Function

    2.5.0 新增

    订阅 store 的 action。handler 会在每一个 action 分发的时候调用并接收 action 描述和当前的 store 的 state 这两个参数:

    store.subscribeAction((action, state) => {
      console.log(action.type)
      console.log(action.payload)
    })

     

    要中止订阅,调用此方法返回的函数便可中止订阅。

    该功能经常使用于插件。详细介绍

registerModule

  • registerModule(path: string | Array<string>, module: Module, options?: Object)

    注册一个动态模块。详细介绍

    options 能够包含 preserveState: true 以容许保留以前的 state。用于服务端渲染。

unregisterModule

  • unregisterModule(path: string | Array<string>)

    卸载一个动态模块。详细介绍

hotUpdate

  • hotUpdate(newOptions: Object)

    热替换新的 action 和 mutation。详细介绍

组件绑定的辅助函数

mapState

  • mapState(namespace?: string, map: Array<string> | Object<string | function>): Object

    为组件建立计算属性以返回 Vuex store 中的状态。详细介绍

    第一个参数是可选的,能够是一个命名空间字符串。详细介绍

    对象形式的第二个参数的成员能够是一个函数。function(state: any)

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可以使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了可以使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

 

mapGetters

  • mapGetters(namespace?: string, map: Array<string> | Object<string>): Object

    为组件建立计算属性以返回 getter 的返回值。详细介绍

    第一个参数是可选的,能够是一个命名空间字符串。详细介绍

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

 

mapActions

  • mapActions(namespace?: string, map: Array<string> | Object<string | function>): Object

    建立组件方法分发 action。详细介绍

    第一个参数是可选的,能够是一个命名空间字符串。详细介绍

    对象形式的第二个参数的成员能够是一个函数。function(dispatch: function, ...args: any[])

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

 

mapMutations

  • mapMutations(namespace?: string, map: Array<string> | Object<string | function>): Object

    建立组件方法提交 mutation。详细介绍

    第一个参数是可选的,能够是一个命名空间字符串。详细介绍

    对象形式的第二个参数的成员能够是一个函数。function(commit: function, ...args: any[])

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

 

createNamespacedHelpers

  • createNamespacedHelpers(namespace: string): Object

    建立基于命名空间的组件绑定辅助函数。其返回一个包含 mapStatemapGettersmapActions 和 mapMutations 的对象。它们都已经绑定在了给定的命名空间上。详细介绍

模块

因为使用单一状态树,应用的全部状态会集中到一个比较大的对象。当应用变得很是复杂时,store 对象就有可能变得至关臃肿。

为了解决以上问题,Vuex 容许咱们将 store 分割成模块(module)。每一个模块拥有本身的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行一样方式的分割:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

 

模块的局部状态

对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

 

一样,对于模块内部的 action,局部状态经过 context.state 暴露出来,根节点状态则为 context.rootState

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

 

对于模块内部的 getter,根节点状态会做为第三个参数暴露出来:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

 

命名空间

默认状况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块可以对同一 mutation 或 action 做出响应。

若是但愿你的模块具备更高的封装度和复用性,你能够经过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的全部 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,

      // 模块内容(module assets)
      state: { ... }, // 模块内的状态已是嵌套的了,使用 `namespaced` 属性不会对其产生影响
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      // 嵌套模块
      modules: {
        // 继承父模块的命名空间
        myPage: {
          state: { ... },
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },

        // 进一步嵌套命名空间
        posts: {
          namespaced: true,

          state: { ... },
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

 

启用了命名空间的 getter 和 action 会收到局部化的 getterdispatch 和 commit。换言之,你在使用模块内容(module assets)时不须要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不须要修改模块内的代码。

在带命名空间的模块内访问全局内容(Global Assets)

若是你但愿使用全局 state 和 getter,rootState 和 rootGetter 会做为第三和第四参数传入 getter,也会经过 context 对象的属性传入 action。

若须要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 做为第三参数传给 dispatch 或 commit 便可。

modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们能够接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

 

在带命名空间的模块注册全局 action

若须要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler中。例如:

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,

      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}

 

带命名空间的绑定函数

当使用 mapStatemapGettersmapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:

computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

 

对于这种状况,你能够将模块的空间名称字符串做为第一个参数传递给上述函数,这样全部绑定都会自动将该模块做为上下文。因而上面的例子能够简化为:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

 

并且,你能够经过使用 createNamespacedHelpers 建立基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

 

给插件开发者的注意事项

若是你开发的插件(Plugin)提供了模块并容许用户将其添加到 Vuex store,可能须要考虑模块的空间名称问题。对于这种状况,你能够经过插件的参数对象来容许用户指定空间名称:

// 经过插件的参数对象获得空间名称
// 而后返回 Vuex 插件函数
export function createPlugin (options = {}) {
  return function (store) {
    // 把空间名字添加到插件模块的类型(type)中去
    const namespace = options.namespace || ''
    store.dispatch(namespace + 'pluginAction')
  }
}

 

模块动态注册

在 store 建立以后,你可使用 store.registerModule 方法注册模块:

// 注册模块 `myModule`
store.registerModule('myModule', {
  // ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})

 

以后就能够经过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。

模块动态注册功能使得其余 Vue 插件能够经过在 store 中附加新模块的方式来使用 Vuex 管理状态。例如,vuex-router-sync 插件就是经过动态注册模块将 vue-router 和 vuex 结合在一块儿,实现应用的路由状态管理。

你也可使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即建立 store 时声明的模块)。

在注册一个新 module 时,你颇有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你能够经过 preserveState 选项将其归档:store.registerModule('a', module, { preserveState: true })

相关文章
相关标签/搜索