Vuex学习

@一棵菜菜  November 12, 2018

相关单词

store 仓库

state 状态

mutation 变更

shared state 共享的状态

Notify actions 通知改变

Backend 后端

dispatch 分发

内容

  1. Vue 应用中原始数据对象的实际来源 - 当访问数据对象时,一个 Vue 实例只是简单的代理访问。所以,如果你有一处需要被多个实例间共享的状态,可以简单地通过维护一份数据来实现共享。——共享数据
const sourceOfTruth = {}

const vmA = new Vue({
  data: sourceOfTruth
})

const vmB = new Vue({
  data: sourceOfTruth
})

这就是为什么不用全局变量而用vuex的原因:

现在当 sourceOfTruth 发生变化,vmA 和 vmB 都将自动的更新引用它们的视图。子组件们的每个实例也会通过 this.$root.$data 去访问。现在我们有了唯一的数据来源,但是,调试将会变为噩梦。任何时间,我们应用中的任何部分,在任何数据改变后,都不会留下变更过的记录。

  1. 所有 store 中 state 的改变,都放置在 store 自身的 action 中去管理。这种集中式状态管理能够被更容易地理解哪种类型的 mutation 将会发生,以及它们是如何被触发。当错误出现时,我们现在也会有一个 log 记录 bug 之前发生了什么。——这就是为什么不用全局变量而用vuex的原因。

3.
store图.png

注意:你不应该在 action 中 替换原始的状态对象 - 组件和 store, 需要引用同一个共享对象,mutation 才能够被观察

我的理解图:仓库里(store实例)管理着许多共享的状态,组件触发更新,然后通知仓库改变对应状态。

  1. 接着我们继续延伸约定,组件不允许直接修改属于 store 实例的 state,而应执行 action 来分发 (dispatch) 事件通知 store 去改变,我们最终达成了 Flux 架构。——各自分工明确

这样约定的好处是,我们能够记录所有 store 中发生的 state 改变,同时实现能做到记录变更 (mutation)、保存状态快照、历史回滚/时光旅行的先进的调试工具。——这就是为什么使用vuex

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

Vuex 和单纯的全局对象有以下两点不同

a. vuex的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

b. 你不能直接改变 store 中的状态改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。

state

单一状态树

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 的组件中需要频繁地导入。

解决办法:

通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。(需调用 Vue.use(Vuex))


new Vue({
  router,
  store,// 在根实例中注册 store 选项
}).$mount('#app')

mapState 辅助函数

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

对象展开运算符

mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。现在有了对象展开运算符可以完成~

 computed:{
    name:function(){/* ... */},
    age(){// age: function(){}
     /* ... */
    },
    // 使用对象展开运算符将此对象混入到外部对象中
    ...mapState([
      'count'
    ])
  }

对象展开运算符:

let x = 1,
y = 2,
z = {a: 3, b: 4};
let n = {x,y,...z};// n={x: 1, y: 2, a: 3, b: 4}

组件仍然保有局部状态

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

Getter

例子:有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数。

mutation

mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {// 类型为 `increment`
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

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

store.commit('increment')

mutation 提交方式

提交载荷的提交方式(Payload)

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)——在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读。

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

对象风格的提交方式(我觉得更直观理解)

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

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

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

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

使用常量替代 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 必须是同步函数

mapMutations

在组件中提交 Mutation

Action

Action 类似于 mutation,不同在于:

  1. Action 提交的是 mutation,而不是直接变更状态。
  2. Action 可以包含任意异步操作
注意:context 对象不是 store 实例本身,context只是与 store 实例具有相同方法和属性的对象。

ES6 的 参数解构 来简化代码

const store = {
    actions: {
    // 与 store 实例具有相同方法和属性的 context 对象
    increment(context) {
      context.commit('increment',{amount:10})
    },
    // 或es6参数解构简化代码:var {name} = {name:1,age:2};name;// 1
    increment({commit}) {
      //取参数对象context里的commit属性,即 commit = context.commit
      commit('increment',{amount:10})
    }
  }
}

分发 Action

Action 通过 store.dispatch 方法触发:

store.dispatch('increment')

在 action 内部执行异步操作:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

Module

例子全(我的)

// mutation-types.js 使用常量替代 Mutation 事件类型
export const SOME_MUTATION = 'SOME_MUTATION';
// store.js
import Vuex from 'vuex';
import {SOME_MUTATION} from './mutation-types';
const store = {
  state: {
    count: 0,
    itemDetail:[
      {
        topic_id:10
      },
      {
        topic_id:30
      }
    ]
  },
  getters: {
    downTodos:(state)=> {
      return state.itemDetail.filter(item=> {
        return item.topic_id > 10
      })
    },
    downTodosCount:(state,getters)=> {
      return getters.downTodos.length;
    }
  },
  mutations: {
    // 类似事件注册:当触发一个类型为`increment`的 mutation 时,调用此函数。
    // mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
    increment(state, payload) {
      state.count += payload.amount;
    },
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION](state) {
    }
  },
  actions: {
    // 与 store 实例具有相同方法和属性的 context 对象
    increment(context) {
      context.commit('increment',{amount:10})
    },
    // 或es6参数解构简化代码:var {name} = {name:1,age:2};name;// 1
    // var {commit} = {commit:function(){},state:{},getters:{},mutations:{}} = context (类似store)
    increment({commit,state},data) {
      //取参数对象context里的commit属性,即 commit = context.commit
      commit('increment',{amount:data.amount})
    }
  }
}

组件中使用

// 调用一个 mutation handler
store.commit('increment',{amount:10})
// 或 对象风格的提交方式
store.commit({
  type:'increment',
  acount: 10
})
store.commit(SOME_MUTATION,{})

我的小结

用dispatch分发action(可以包含任意异步操作),用commit提交mutation(必须是同步函数),将调用mutations的handler,再(执行回调函数)改变状态。


添加新评论