Vuex


Vuex

Vuex是一种状态管理器,它集中管理应用中所有组件的状态,并约定了状态更新的规则。Vuex的存在主要是为了解决多组件间的数据共享和更新问题。

Vuex适合大型单页面应用,小页面或局部状态管理时,可使用简单的store模式进行状态管理。如下:

创建store对象,并放置到data中;store对象中的state变更,都通过自身的 action 集中管理;这种集中式状态(数据)管理能够统一状态的出入口,便于跟踪和记录状态的更新过程,同时在使用时也能够让代码意图更加明显

// store对象
var store = {
  debug: true,
  state: {
    message: 'Hello!'
  },
  setMessageAction (newValue) {
    if (this.debug) console.log('setMessageAction triggered with', newValue)
    this.state.message = newValue
  },
  clearMessageAction () {
    if (this.debug) console.log('clearMessageAction triggered')
    this.state.message = ''
  }
}

// Vue实例
var vmA = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})


State

Vuex中的数据和Vue实例中的data遵循相同的规则;如下,将store对象提供给Vue实例中的“store”选项,就可以把store对象注入到所有子组件,且子组件能通过this.$store访问到:

const app = new Vue({
  el: '#app',
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})


Getters

Getters可以返回属性或方法;Getters返回的属性相当于store实例的计算属性,其返回值会被缓存起来,只有当依赖值发生了改变才会被重新计算。

1>. Getters返回属性

// Vuex
state: {
    todos: [
       { id: 1, text: '...', done: true },
       { id: 2, text: '...', done: false }
    ],
 },
getters: { 
    doneTodos: state => {
       return state.todos.filter(todo => todo.done);
    },
    doneTodosCount:state => {
       return state.todos.length;
    },
    doneTodosCount1: (state, getters) => { //  也可以接受其他参数,如:getter作为第二个参数
       return getters.doneTodos.length;
    }
},

// 使用
this.$store.getters.doneTodos
this.$store.getters.doneTodosCount

2>. Getters返回函数

// Vuex -> Getters
getters: { 
   getTodoById: (state) => (id) => {
      return state.todos.find(todo => todo.id === id)
   }
},

// 使用:getter方法不会缓存,每次都会调用执行
this.$store.getters.getTodoById(2)


Mutation

更改store中状态的唯一方法是提交mutationmutation非常类似于事件,如下:increment事件类型 (type),increment是事件回调 (handler):

// Vuex
mutations: {
   increment (state) {
      // 变更状态
      state.count++
   }
},

// 使用
store.commit('increment')

1>. 提交载荷(Payload)

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

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

// 使用
store.commit('increment', 10)

2>. 提交载荷(Payload) - 以对象方式

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

// 方式一:对象作为参数
store.commit('increment', {
  amount: 10
})

// 方式二:直接以对象方式提交,在对象中标明mutations事件类型即可,increment回调函数在定义上不用作出任何改变
store.commit({
  type: 'increment',
  amount: 10
})

3>. 常量命名Mutation事件类型

使用常量命名Mutation事件类型常用于大型项目,或为了便于第三方工具分析代码之类的用途,如下:

mutations: {
  [SOME_MUTATION] (state) {  // 使用常量作为函数名
    // mutate state
  }
}

4>. Mutation必须是同步函数

mutation回调函数中的代码必须是同步的;因为mutation回调函数的存在,很大程度上是为了记录状态变更和代码调试,如果其中的代码是异步执行,则状态记录会变的不准确,且不可追踪。

例如:调用了两个包含异步回调的 mutation 来改变状态,程序中根本就无法确定哪个先执行,哪个后执行。


Action

Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态,并且 Action 可以包含异步操作,如下:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit 提交 mutation,或通过 context.state 和 context.getters 来获取 state 和 getters。

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码,特别是需要多次调用 commit 时:

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

1>. 分发 Action

Action 通过 store.dispatch 方法触发:

store.dispatch('incrementAsync')

Actions支持载荷和对象方式进行分发:

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

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

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

Action 常用于异步调用和分发多重 mutation 的场景,如下:

actions: {
  checkout ({ commit, state }, products) {
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求,然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

2>. 组合 Action

 store.dispatch 可以处理 action 函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

// Vuex
actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

// 使用
store.dispatch('actionA').then(() => {
  // ...
})

Action 中也可以触发其它 Action:

actions: {
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

也可以使用 async/await 或 组合 action:

// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}


Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。为了防止 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 的状态

1>. 局部状态

对于模块内部的 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
    }
  }
}

2>. 命名空间

默认情况下,模块内的 action、mutation 和 getter 注册在全局命名空间中,这使得各个模块都能够对同一类型名称的 mutation 或 action 作出响应。通过添加 namespaced: true 使模块带有命名空间,之后在外部调用时就需要标明命名空间,但同一模块中的 action、getters 相互调用时无须指明命名空间。无论是否有命名空间,模块内都可以获取到全局或和局部的state对象,并相互调用;

调用时,模块名称作为路径名称,模块之间可持续向下嵌套:

$store.commit("moduleA/increment")
$store.commit("moduleA/moduleB/increment")

详见:命名空间

3>. 模块动态注册

使用 store.registerModule 方法可动态注册模块;动态注册模块使得第三方 Vue 插件可通过在 store 中附加新模块的方式来使用 Vuex 管理状态。

详见:模块动态注册


辅助函数

mapState、mapGetters、mapMutations、mapActions等辅助函数仅仅是将 store 中的 state、getter悔恨映射到Vue实例的局部属性;


官网对Vuex作了详细描述,建议多多阅读官方文档:https://vuex.vuejs.org/zh/

举报

© 著作权归作者所有


1