今天来学习一下vuex
前言
为什么会出现vuex?
- 1.如何让多个vue组件共享状态?
- 2.vue组件间如何通讯?
通常,在项目不是很复杂的时候,我们会利用全局事件总线解决,但是随着复杂度的提升,这些代码将变得难以维护。因此,我们需要一种更加好用的解决方案,于是,vuex诞生了!
什么是vuex?
- Vuex是一个专门为Vue.js应用设计的状态管理架构,统一管理和维护各个vue组件的可变化状态(vue组件里面的某些data)
- 它采用集中式存储管理应用所有组件的状态,并以响应的规则保证状态以一种课预测的方式发生变化
- Vue五个核心概念,state,getters,mutations,action,modules
使用前奏
- 1.npm安装
npm install vuex
- 2.Vue.use()
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
五大核心概念
- state
- getters
- mutations
- actions
- modules
一个store例子
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
现在,你可以通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更:
store.commit('increment')
console.log(store.state.count) // -> 1
在App.vue文件中添加代码,在templete模板中添加一个按钮和点击事件
<button v-on:click="addClick">加</button>
在
import store from './vuex/store'
methods:
{
addClick:function(){
store.commit('increment')
console.log(store.state.count) // -> 1
}
}
State
state是一个全局的状态存储,数据会存储在其中,vue组件可以直接访问其中的值,但是只可以读,不可以进行写操作
Getter
有些时候我们需要对获取的数据进行加工,获取数据的一部分或者某些属性,而不是直接获取state中的数据,这时候可以通过getter定义函数,返回对应的数据
Mutations
mutations是vuex中唯一一个可以修改数据的地方,mutations可以定义事件函数,在vue组件中可以通过commit发射事件,调用函数。需要注意的是,mutations中的操作必须是同步的,不可以存在异步操作的情况
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutations 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 改变状态
state.count++
}
}
})
你不能直接调用一个 mutation 回调函数。要唤醒一个 mutation,你调用 store.commit 方法(参数为回调函数名):
store.commit('increment')
mutation回调函数的第一个参数始终为store,你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(playload):
// ...
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
})
Mutations的提交方式
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
store.commit({
type: 'increment',
amount: 10
})
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
你可以在组件中使用 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({
add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
})
}
}
Action
和mutation比较相似,不同的是cations中不直接修改state,而是通过commit调用mutations修改数据,而且actions中可以存在异步处理逻辑
- action提交的是mutation,而不是直接变更状态
- action可以包含任意异步操作
可以把mutation比作仓库的管理员,负责仓库管理,而把action比作领导,可以命令仓库管理员作操作,但不会亲自动手。
一个例子
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
actions 的第一个参数是 context,它向外暴露一组与 store 实例相同的方法/属性,所以可以直接调用 context.commit 或者访问 context.state 或者 context.getters 。我们通常使用 es6 的参数解构来简化我们的代码,直接写成{ commit }
actions: {
increment ({ commit }) {
commit('increment')
}
}
Action 通过 store.dispatch 方法触发:store.dispatch(‘increment’)
action多此一举吗?
mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
action的提交方式
- 1.载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
- 2.以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
- 3.在组件中分发
在组件中使用this.$store.dispatch('xxx')
分发 action,或者使用mapActions
辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment' // 映射 this.increment() 为 this.$store.dispatch('increment')
]),
...mapActions({
add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')
})
}
}
组合action
action通常是异步的,那么如何知道action什么时候接触呢?如何组合多个action呢?
第一件事是store.dispatch的返回的是被触发的action函数的返回值,因此你可以在action中返回Promise:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
就可以使用then()
store.dispatch('actionA').then(() => {
// ...
})
在另一个action中也可以
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
最后,如果我们利用 [async / await] 这个 JavaScript 即将到来的新特性,我们可以像这样组合 action:
假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
Modules
使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store对象会变得臃肿不堪。
为了解决以上问题,Vuex运行将store分割到多个模块(module)。每个模块拥有自己的state,mutation,action,getters,甚至时嵌套子模块—从上至下进行类似的分割:
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: {
incrementIfOdd ({ state, commit }) {
if (state.count % 2 === 1) {
commit('increment')
}
}
}
}
对于模块内部的 getter,根节点状态会作为第三个参数:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
一个vuex的应用实例
- store.js
export default new Vuex.Store({
state: {
messages: []
},
actions: {
newMessage ({commit}, msg) {
commit('NEW_MESSAGE', msg)
}
},
mutations: {
NEW_MESSAGE (state, msg) {
state.messages.push(msg)
}
}
})
- main.js
import Vue from 'vue'
import App from './App.vue'
import store from './vuex/store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
- client.vue
<template>
<div>
<h1>{{ clientid }}</h1>
<div>
<ul>
<li v-for="message in messages">
<label>{{ message.sender }}:</label> {{ message.text }}
</li>
</ul>
<div>
<input v-model="msg" placeholder="Enter a message, then hit [enter]" @keyup.enter="trySendMessage">
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data() {
return {
msg: ''
}
},
props: ['clientid'],
computed:{
...mapState({
messages: state => state.messages
})
},
methods: {
trySendMessage() {
this.newMessage({
text: this.msg,
sender: this.clientid
})
this.resetMessage()
},
resetMessage() {
this.msg = ''
},
...mapActions(['newMessage'])
}
}
</script>
<style>
</style>
- App.vue
<div>
<client clientid="Client A"></client>
</div>
<div>
<client clientid="Client B"></client>
</div>
<script>
import client from './components/Client.vue'
import store from './vuex/store'
export default {
name: 'app',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
components:{client}
}
</script>
组件之间的信息共享成功!