Vuex文档回顾

615 阅读4分钟

工欲善其事,必先利其器





项目中因为Vuex使用的不纯熟,导致各路api回炉重造。现在回顾一边Vuex的知识后再次回去重新整改相关的逻辑。Vuex的内容不多,所以我们可以简单而快速的回顾。

  • State

存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,你可以甚至把Vuex当作是一个插入全局的“组件”。

最简单的使用:

// 放入computed内,一旦vuex的数据更新,组件也可以接收到,立马改变相应的数据,如下,count是响应式的
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

Vuex 通过 store 选项,从根组件“注入”到每一个子组件(需要提前Vue.use(Vuex)):

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

// 其子组件可以任意访问
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count // 访问Vuex实例属性
    }
  }
}

mapState 辅助函数

mapState并不是必须学习的内容, 但他能使你使用vuex的时候更加方便(类似于箭头函数一样,感觉上就比较爽)。但是某种意义上说,其实你是必须要使用它的。

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
    }
  })
}
// 同名的时候还可以简写为
computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])
  • Getter

你可以把他看为vuex版本的computed。

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

// 只有一两个还好,数量一旦多起来,你会发现mapState给你带来的便利统统消失
// 你又再次的需要一次又一次的将state映射为computed
computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

// 于是乎,出现了Getter,也就是vuex版本的computed
// 你可以像引入state一样引入它,这样你又可以解决一个麻烦了
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {  // getter可接受2个参数,这里只写了一个,第二个参数可以为其他getters
      return state.todos.filter(todo => todo.done)
    }
  }
})

// getters第二个参数的用法
getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}

// 我们可以让getter返回一个方法,但这么做,就不会缓存结果了,也就是你其实就是调用了一个普通的函数,不会像computed。
getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}

// 我们来看看如何引入它
computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}

// mapGetter 也是必不可少的
import { mapGetters } from 'vuex'
export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}
  • Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

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

怎么提交,commit呗,英文直译,通俗易懂

store.commit('increment')

//我们可以提交参数的(废话)

mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10) 

这个参数(载荷)最好是个对象

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

store.commit('increment', {
  amount: 10
})
// 另外一种提交的方式(名曰对象提交大法)
store.commit({
  type: 'increment',
  amount: 10
})

Mutation 需遵守 Vue 的响应规则,Vuex其实更像一个Vue的组件,子类。所以你必须要对他的数据进行和Vue一样的规范。

  1. 最好提前在你的 store 中初始化好所有所需属性。

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

使用 Vue.set(obj, 'newProp', 123), 或者 以新对象替换老对象。例如,利用对象展开运算符我们可以这样写:

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

- Mutation 必须是同步函数

现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。

按道理来说,每一条 mutation 被记录,devtools 都需要捕捉到触发前和触发后状态。

然而,在上面的例子中 mutation 中的异步函数无法正常捕捉触发后状态:

因为当 mutation 触发的时候,回调函数最早也在下一循环才会被调用(事件循环机制),devtools 不知道什么时候回调函数实际上被调用——这将使得工具不可追踪,时间穿梭版的调试体验也无从谈起了。

- mapMutation 也是必不可少的

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

你并不是必须使用Action的,请查看你的项目是否是大型项目或者多人配合。不然你可以在自己的逻辑里调用异步来提交mutation而不是将其写入action。单纯的说是不要为了action而action。

Action 类似于 mutation,不同在于:

  1. Action 提交的是 mutation,而不是直接变更状态。
  2. Action 可以包含任意异步操作。

这使得他可以解决mutation无法解决的问题。(可以叫他异步mutation)。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment') //你现在可以把context视为store,你碰到问题了自然会意识到他与store的不同,或者你自行查阅
      
    }
  }
})

// 还记得commit嘛,这是触发mutation的方法,action使用的是dispatch(分发)
store.dispatch('increment')

以下是带入参数的写法

// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

我们来看一个购物车的实例 (这个例子我真的服了,懂的人估计都难看懂……)

actions: {
  checkout ({ commit, state }, products) {
    // 把当前购物车的物品存入变量
    const savedCartItems = [...state.cart.added]
    // 发出结账请求,并且清空购物车,此处的types.CHECKOUT_REQUEST对应一个字符串,例如: ‘CHECKOUT_REQUEST’
    // 这个mutation将清空state.cart.added的数据。state.cart.added = []。
    // 然后可能会改变一个变量,拉起一个“正在结算中。。。”的动画
    commit(types.CHECKOUT_REQUEST)
    // 发起结算的请求,可能使用的是ajax,因为此处使用了回调的方式。
    shop.buyProducts(
      products, // 未解,不知道干啥用的,这个例子好空洞。
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS)// 成功了,就把“正在结算中。。。”的动画关了
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)// 将购物车内的变量重新放回。
    )
  }
}

- mapActions 也是必不可少的

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')`
    })
  }
}

- 组合 Action

store.dispatch 可以处理 action 函数返回的 Promise,并且 store.dispatch这个方法本身也返回 Promise:

// 可以看到这个action 函数返回的 Promise
actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}
// 诺,他自己也是个promise
store.dispatch('actionA').then(() => {
  // ...
})

// 两个action组合 ,触发了A结束后,B才会触发
actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

//使用async更加简洁了
actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

我相信这个异步action东西相当一部分刚入门的小伙伴无法理解,还觉得写的什么垃圾玩意,确实写不好哈。。。不过指个方向,如果刚刚入门,完全可以不管action,这个只是vuex锦上添花的东西。未来某个时刻你会意识到promise的重要,并且学习完它后再回头看vuex,发现不过如此。如果你现在想去学习promise,推荐阮一峰老师的教程并且手动尝试去使用es5完善一个promise,这将会使你更加强大。

不说modules~