Vue学习笔记(十一) Vuex
1、介绍
Vuex 是一个为 Vue 应用程序开发的状态管理模式,它用集中式存储来管理应用所有组件的状态
简单来说,它的作用就是把所有组件的共享状态抽取出来,以一个全局单例的模式进行管理
我们可以把 Vuex 理解成一个 store,里面存储着所有组件共享的 state(数据)和 mutations(操作)
这里还是先附上官方文档的链接:https://vuex.vuejs.org/zh/,有兴趣的朋友可以去看看
2、安装
(1)通过 CDN 引用
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
(2)通过 NPM 安装与使用
- 安装
> npm install vuex
- 使用
在项目中需要通过 Vue.use()
明确安装 Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
3、State
Vuex 中的 state 用于集中存储数据,当我们需要访问 state 时,可以先将其映射为计算属性
由于 state 是响应式的,所以当 state 发生变化时,它会重新求取计算属性,并自动更新相应的 DOM
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<my-counter></my-counter>
</div>
<script>
// 1、定义 store
const store = new Vuex.Store({
state: { // 定义 state
count: 0
}
})
// 2、定义组件
const Counter = {
template: `
<div>
<p>{{ count }}</p>
</div>
`,
// 如果需要访问 state,可以在计算属性中通过 this.$store.state 返回数据
computed: {
count() {
return this.$store.state.count
}
}
}
// 3、创建并挂载根实例
const app = new Vue({
store, // 注入 store
components: {
'my-counter': Counter
}
}).$mount('#app')
</script>
</body>
</html>
现在假设我们需要在一个组件中使用多个 state,如果为每个状态都写一条语句将其映射为计算属性未免太过繁琐
所以 Vuex 提供 mapState()
辅助函数能够帮助我们完成这些工作
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<my-counter></my-counter>
</div>
<script>
const store = new Vuex.Store({
state: {
counter1: 0,
counter2: 10,
counter3: 100
}
})
const Counter = {
template: `
<div>
<p>couter1: {{ counter1 }}</p>
<p>couter2: {{ counter2 }}</p>
<p>couter3: {{ counter3 }}</p>
</div>
`,
data: function () {
return {
localCounter: 1
}
},
computed: {
// mapState() 返回一个对象,使用对象展开运算符将其混入 computed 对象
...Vuex.mapState({
// 使用箭头函数,可以简化代码
counter1: state => state.counter1,
// 使用字符串 'counter2',等价于 `state => state.counter2`
counter2: 'counter2',
// 使用常规函数,可使用 `this` 以获取局部状态
counter3 (state) {
return state.counter3 + this.localCounter
}
})
}
}
const app = new Vue({
store,
components: {
'my-counter': Counter
}
}).$mount('#app')
</script>
</body>
</html>
5、Getter
Vuex 中的 getter 用于管理派生出来的状态
它就相当于计算属性一样,会被缓存起来,当依赖发生改变时才会重新计算
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<my-todo></my-todo>
</div>
<script>
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: 'Say Hello', done: true},
{ id: 2, text: 'Say Goodbye', done: false}
]
},
getters: { // 定义 getters
// 其接受 state 作为第一个参数
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
// 其接受 getters 作为第二个参数
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
})
const Todo = {
template: `
<div>
<p>{{ doneTodosCount }} task(s) done</p>
<ul><li v-for="item in doneTodos">{{ item.text }}</li></ul>
</div>
`,
computed: {
doneTodos () {
return this.$store.getters.doneTodos
},
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
}
const app = new Vue({
store,
components: {
'my-todo': Todo
}
}).$mount('#app')
</script>
</body>
</html>
和 state 一样,getters 也有一个名为 mapGetters()
的辅助函数将其映射为计算属性
computed: {
...mapGetters([
'doneTodos',
'doneTodosCount'
])
}
如果要给 getter 重命名,可以用对象形式
computed: {
...mapGetters({
doneTodosAlias: 'doneTodos',
doneTodosCountAlias: 'doneTodosCount'
})
}
5、Mutation
上面我们讲了怎么访问 state,下面我们来看看怎么修改 state,改变状态的唯一方法是提交 mutation
这里,请记住一条重要的规则:mutation 必须是同步函数
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<my-counter></my-counter>
</div>
<script>
const store = new Vuex.Store({
state: {
count: 0
},
mutations: { // 定义 mutations
// 传入的第一个参数是 state
increment(state) {
state.count += 1
},
// 传入的第二个参数是 payload,可以提供额外的信息
incrementN(state, payload) {
state.count += payload.amount
}
}
})
const Counter = {
template: `
<div>
<p>{{ count }}</p>
<button @click="increment"> 加 1 </button>
<button @click="incrementN"> 加 10 </button>
</div>
`,
// 如果需要访问 state,可以在计算属性中通过 store.state 返回数据
computed: {
count() {
return store.state.count
}
},
// 如果需要修改 state,可以通过 store.commit() 提交 mutation
methods: {
increment() {
store.commit('increment')
},
incrementN() { // 以对象的形式给 commit() 传入 payload
store.commit('incrementN', {
amount: 10
})
}
}
}
const app = new Vue({
store,
components: {
'my-counter': Counter
}
}).$mount('#app')
</script>
</body>
</html>
当然,我们也可以使用 mapMutations()
辅助函数将 mutation 映射为 methods
methods: {
...mapMutations([
'increment',
'incrementN'
])
}
也同样可以使用对象形式支持重命名
methods: {
...mapMutations({
add: 'increment',
addN: 'incrementN'
})
}
6、Action
还记得上面我们说过 mutation 只能是同步函数,若需要使用异步操作,则可以通过分发 action
action 内部可以包含异步逻辑,它做的工作是提交 mutation,而不是直接改变状态
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<my-counter></my-counter>
</div>
<script>
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count += 1
},
incrementN(state, payload) {
state.count += payload.amount
}
},
actions: { //定义 actions
// 该方法接受一个与 store 实例具有相同属性和方法的对象作为参数
// 我们可以调用 context.commit() 提交 mutation
// 也可以通过 context.state 和 context.getters 访问 state 和 getters
incrementAsync(context) {
setTimeout(() => {
context.commit('increment')
}, 1000)
},
// 和 mutations 一样,也可以传入第二个参数 payload
incrementNAsync(context, payload) {
setTimeout(() => {
context.commit('incrementN', payload)
}, 1000)
}
}
})
const Counter = {
template: `
<div>
<p>{{ count }}</p>
<button @click="increment"> 同步加 1 </button>
<button @click="incrementN"> 同步加 10 </button>
<button @click="incrementAsync"> 异步加 1 </button>
<button @click="incrementNAsync"> 异步加 10 </button>
</div>
`,
computed: {
count() {
return store.state.count
}
},
methods: {
// 通过 store.commit() 提交 mutation
increment() {
store.commit('increment')
},
incrementN() { // 以对象的形式给 commit() 传入 payload
store.commit('incrementN', {
amount: 10
})
},
// 通过 store.dispatch() 分发 action
incrementAsync() {
store.dispatch('incrementAsync')
},
incrementNAsync() { // 以对象的形式给 dispatch() 传入 payload
store.dispatch('incrementNAsync', {
amount: 10
})
}
}
}
const app = new Vue({
store,
components: {
'my-counter': Counter
}
}).$mount('#app')
</script>
</body>
</html>
如果需要处理更复杂的异步逻辑,我们也可以使用 Promise 和 async/await
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<my-counter></my-counter>
</div>
<script>
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
add(state) {
state.count += 10
},
multiply(state) {
state.count *= 10
}
},
actions: {
addAsync(context) {
setTimeout(() => {
context.commit('add')
}, 1000)
},
multiplyAsync(context) {
setTimeout(() => {
context.commit('multiply')
}, 1000)
},
// 只允许使用异步函数 addAsync 和 multiplyAsync,实现先乘后加
// 使用 async/await
async multiplyBeforeAdd(context) {
await context.dispatch('multiplyAsync')
context.dispatch('addAsync')
},
// 只允许使用异步函数 addAsync 和 multiplyAsync,实现先加后乘
// 使用 async/await
async addBeforeMultiply(context) {
await context.dispatch('addAsync')
context.dispatch('multiplyAsync')
}
}
})
const Counter = {
template: `
<div>
<p>{{ count }}</p>
<button @click="done"> 乘10,加10,加10,乘10 </button>
</div>
`,
computed: {
count() {
return store.state.count
}
},
methods: {
// 先完成先乘后加,再完成先加后乘
done() {
store.dispatch('multiplyBeforeAdd').then(() => {
store.dispatch('addBeforeMultiply')
})
}
}
}
const app = new Vue({
store,
components: {
'my-counter': Counter
}
}).$mount('#app')
</script>
</body>
</html>
【 阅读更多 Vue 系列文章,请看 Vue学习笔记 】