Vuex

2018-09-28  本文已影响0人  岛在深海处

Vuex就是一个保存、管理共享数据的容器。没有Vuex之前,组件之间数据共享需要通过props与$emit,有了Vuex后组件之间共享数据更便于调试。

如果不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的,简单应用自己使用一个store模式就足够了。Vuex更适合构建一个中大型单页应用

Vuex 使用单一状态树。就是说每个应用将仅仅包含一个 store 实例,store实例从根组件“注入”到每一个子组件中,所有子组件都能使用this.$store访问到。

1、state:

响应式的,state数据变化,组件就会调用render重新刷新。需要将获取state的操作放入computed中,这样当state中的数据改变时,computed会重新计算并且缓存。
state可通过this.$store.state来访问。

Vue响应式原理:Vue 将遍历 Vue 实例的 data 对象中的所有的属性并使用 Object.defineProperty 把这些属性全部转为 getter/setter,当setter方法被调用时,将会通知watcher,然后调用render重新刷新对应组件。

2、getter:
//getters直接定义在store中
getters: {
    /**
    * state:状态(共享数据)
    * getters:相当于this.$store.getters
    */
    param1: (state, getters) => {...}
}
//getters定义在模块中
getters: {
    /**
    * state:局部状态(模块内的共享数据)
    * getters:局部getters(模块内的getters)
    * rootState:根状态(根共享数据)
    * rootGetters:根getters
    */
    param1: (state, getters, rootState, rootGetters) => {...}
}

当需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数时,就需要在组件的computed中做操作:

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

假设多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想
而getter正好解决了这问题,与Vue的计算属性类似,getter相当于store的计算属性。
getter可通过this.$store.getters访问。有两种方式访问:一种时访问属性,会缓存;另一种时访问方法,不会缓存

3、Mutation
//mutations直接定义在store中的方式。
mutations: {
    /**
    * state: 状态(定义的所有的共享数据)
    * payload: 载荷(而外参数,一般为一个对象)
    */
    param1: (state, payload) => {...}
}
//mutations定义在Module中的方式。
mutations: {
    /**
    * state: 局部状态(模块内的共享数据)
    * payload: 载荷(而外参数,一般为一个对象)
    */
    param1: (state, payload) => {...}
}

在多人合作的项目中,我们可以使用ES2015 风格的计算属性命名功能来使用一个常量作为函数名:

// 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
    }
  }
})

Vuex中更改store状态的唯一方法是通过提交Mutation:

this.$store.commit('param1', {
  amount: 10
})

或者对象风格提交方式,整个对象都作为载荷传给 mutation 函数

this.$store.commit({
  type: 'param1',
  amount: 10
})

模块内使用需要:this.$store.commit('ModuleA/getAllProducts')

4、Action
actions: {
    /*
    * context: 与 store 实例具有相同方法和属性的对象,但是不是store实例本身
    *             注意这里的context可以采用解构赋值。
    *             在Module中context同样也包含了rootState对象。
    * payload:载荷(额外参数)
    * return:不一定要有return,只有在需要组合Action时才需要用到。因为dispatch函数可以返回一个Promise对象。
    */  
    param1: (context, payload) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                commit('someMutation')
                resolve()
            }, 1000)
        })
    }
}

Action主要用来弥补mutation无法进行异步操作的缺陷,Action无法直接修改数据,同样也是提交mutation,只是把提交mutation的操作放到了异步回调中。

Action 通过 this.$store.dispatch 方法触发

模块内使用需要:this.$store.dispatch('ModuleA/getAllProducts')

5、Module

基本流程:

/*----------------------moduleA.js------------------------*/
const state = {...}
const mutations = {...}
const getters = {...}
const actions = {...}

export default {
    namespace: true,  //这个还没太理解
    state,
    getters,
    actions,
    mutations
}

/*----------------------moduleB.js------------------------*/
const state = {...}
const mutations = {...}
const getters = {...}
const actions = {...}

export default {
    namespace: true,
    state,
    getters,
    actions,
    mutations
}

/*----------------------store.js------------------------*/
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './moduleA.js'
import products from './moduleB.js'

Vue.use(Vuex)
export default new Vuex.Store({
  modules: {
    moduleA,
    moduleB
  }
})

/*----------------------app.js------------------------*/
import store from './store.js'
new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

/*----------------------ComponentA.vue------------------------*/

注意在模块中我们可以通过参数rootState.模块名.state属性名拿到其他模块的状态。

问题1:namespace: true

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,所以在调用这些对象内的方法时不需要指定模块路径,如果加上namespace: true那么在组件的methods中映射store.dispatch()时需要这样做:

methods:{
    ...mapActions([
        'moduleA/action1',  //相当于this.$store.dispatch('moduleA/action1', {...})
        'moduleB/action2'
    ])
}
可以简写为:
methods: {
    ...mapAction('moduleA', [
        'action1', 
        'action2'
    ])
}
问题2:异步修改state时,为什么要将异步操作写在Action中,为什么不直接在回调提交mutation?

与getter相似。实际上直接在异步回调函数中提交mutation也是可行的,但是异步操作无法复用,如果几个组件都要用到这个异步操作,那就得在每个组件中去写一遍,太过于繁琐。
注意:这里说的是在回调函数种提交mutation,而不是在mutation中写异步回调,mutation中必须是同步事物。异步会使程序devtools很难调试,因为如果有多个异步回调去改变状态,devtools无法确定哪个先执行。

问题3:Action的第一个参数context为什么不是store实例本身?

因为在Module中context对象包含了局部state与rootState,如果是store实例本身怎么会有局部state呢!

问题4:Vue响应式原理?

Vue响应式原理:Vue 将遍历 Vue 实例的 data 对象中的所有的属性并使用 Object.defineProperty 把这些属性全部转为 getter/setter,当setter方法被调用时,将会通知watcher,然后调用render重新刷新对应组件。

上一篇 下一篇

猜你喜欢

热点阅读