目录
11.1状态管理与应用场景
1)state
2)Getters
3)Mutations
4)Actions
5)Module
11.2Vuex的安装与基本应用
11.3Vuex的核心概念
一句话解释vuex:就是单独成立一个组件,这个组件存储共享的数据,其他组件都可以从这个共享组件里面抽取数据。这就是vuex的作用。
11.1状态管理与应用场景
本章主要讲解了Vuex的基本用法。通过本章的学习,掌握Vuex的state、getters、mutations、actions等核心概念,掌握如何使用Vuex进行状态管理。
状态管理,管理的是全局状态,即全局变量。Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
大白话:Vuex 是一个插件,可以帮我们管理 Vue 通用的数据 (多组件共享的数据)。例如:购物车数据 个人信息数
状态(state):驱动应用的数据源,即组件中的data;
视图(view):以声明方式将状态映射到视图,如{{counter}};
操作(action):响应在视图上的用户输入导致的状态变化,即组件的函数methods。
1)state
state是存储的单一状态,是存储的基本数据。
2)Getters
getters是store的计算属性,对state的加工,是派生出来的数据。就像computed计算属性一样,getter返回的值会根据它的依赖被缓存起来,且只有当它的依赖值发生改变才会被重新计算。
3)Mutations
mutations提交更改数据,使用store.commit方法更改state存储的状态。(mutations同步函数)
4)Actions
actions像一个装饰器,提交mutation,而不是直接变更状态。(actions可以包含任何异步操作)
5)Module
Module是store分割的模块,每个模块拥有自己的state、getters、mutations、actions。
在较大型的项目中,将有许多组件用到同一变量,比如,一个登录的状态,很多页面组件都需要这个信息。在这样的情景下,使用Vuex进行登录状态的统一管理就很方便。当然,虽然麻烦但也可以时刻在对应页面操作cookie。所以,状态管理不是必需的,所有状态管理能做的,都能用其它方式实现,但是状态管理提供了统一管理的地方,操作方便,也更加明确。但一些状态只是父组件和子组件共享,不推荐使用状态管理实现,而用$emit和props即可简单实现。
11.2Vuex的安装与基本应用
1.安装Vuex
与VueRouter一样,将Vuex添加到项目中也有4种主要方法:本地独立版本方法、CDN方法、NPM方法以及命令行工具(VueCLI)方法。
2.项目文件中导入并显式地使用Vuex
使用VueCLI安装Vuex后,首先,在项目的/src/store/index.js文件中,导入Vuex模块,并创建一个store(仓库)。
然后,在项目主文件main.js中导入Vuex,并显式地使用Vue实例调用Vuex。
import { createApp } from 'vue'
import App from './App.vue'
import store from './store' //导入store目录中的index.js,Vuex的创建与配置在该文件中
createApp(App).use(store).mount('#app')
11.3Vuex的核心概念
Vuex应用的核心是store,即仓库。store实际上就是一个容器,它包含应用中大部分的状态(state),与单纯的全局对象不同,主要有两点区别。
(1)Vuex的状态存储是响应式的。也就是说,当Vue实例或组件从仓库store中读取状态时,若store中状态发生变化,那么相应的Vue实例或组件也会高效更新。
(2)用户不能直接更新store中的状态。更新的唯一途径是显式地提交mutation(类似于事件),以便跟踪每一个状态的变化。
一个完整的store包含state、getters、mutations、actions、modules五大组成部分。
11.3.1 Vuex中的state
Vuex使用单一状态树,即使用一个对象包含了所有的应用层级状态,作为一个唯一的数据源而存在。也就是说,每个应用将仅包含一个仓库store实例。因此,需要状态跟踪(管理)的数据保存在Vuex选项的state选项内。
1.在Vue 组件中通过computed计算属性获得 Vuex状态
$store与store的区别
l $store是挂载在Vue 实例上的(即Vue.prototype),而组件也是一个Vue实例。在组件中可使用this访问原型上的属性,template拥有组件实例的上下文,可直接通过{{$store.state }}访问,等价于script 中的this.$store.state。
l store是挂载到Vue上,为Vue的根实例;store引入后被注入到Vue上,成为Vue的原型属性,所以store是挂载到Vue上,为Vue的根实例;store引入后被注入到Vue上,成为Vue的原型属性,所以在script中通过store.state和$store.state都可以访问。
l 至于{{store.state}},script中的data需声明过store才可以访问。
2.在Vue 组件中通过mapState()辅助函数获得Vuex 状态
当一个组件需要获取多个状态时,将这些状态都声明为计算属性会有些重复和冗余。为解决这个问题,Vuex通过使用 mapState() 辅助函数帮助生成计算属性,减少按键次数。
mapState() 辅助函数返回的是一个对象,用来获取多个状态。mapState()可以接收{}或[]作为参数。
{}参数为键值对形式参数,即key:value,key为计算属性,value为函数,参数为store.state,返回需要的state。
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState(state) {
return state.count + this.localCount
}
})
当映射的计算属性名称与state的子节点名称相同时,可以为mapState()传一个字符串数组参数。
computed: mapState([
// 映射 this.count 为 store.state.count
'count' //可以有多个state对象中属性,用逗号分隔
])
3.对象展开运算符
mapState()函数返回的是一个对象。如何将它与局部计算属性混合使用呢?通常,首先需要使用一个工具函数将多个对象合并为一个,然后将最终对象传给
computed 属性。但自从有了对象展开运算符(…),可以极大地简化写法。
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
app.vue
app是从共享组件中读取数据,这里有两种方式,
方法一
this.$store.state.bookPress
方法二
...mapState(['BISBN', 'bookPrice', 'bookAuthor']),注意这里的三个点的含义是打散的意思,我们看看下面的这个例子,.mapState数据赋值给变量S,然后在控制台打印,发现是个键值对,key是组件state里面的对象key。
let s = mapState(['BISBN', 'bookPrice', 'bookAuthor'])
...mapState(['BISBN', 'bookPrice', 'bookAuthor']),最后就会变成
BISBN:ƒ mappedState()
bookPrice:ƒ mappedState()
bookAuthorƒ mappedState()
<template><div><h3>{{bookName}}</h3><h3>作者:{{$store.state.bookAuthor}}</h3><h3>出版社:{{$store.state.bookPress}}</h3><h3>ISBN:{{$store.state.BISBN}}</h3><h3>价格:{{$store.state.bookPrice}}</h3></div><hr/><div><h3>{{bookName}}</h3><h3>作者:{{bookAuthor}}</h3><h3>出版社:{{bookPress}}</h3><h3>ISBN:{{BISBN}}</h3><h3>价格:{{bookPrice}}</h3></div>
</template>
<script>
import { mapState } from 'vuex'
export default {name: 'App',data() {//组件中的私有数据return {bookName : 'Vue.js '}},//使用计算属性获取store中的状态数据computed: {bookPress() {return this.$store.state.bookPress},//使用对象展开运算符获取store中的状态数据...mapState(['BISBN', 'bookPrice', 'bookAuthor']),}
}
</script>
<style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>
index.js
import { createStore } from 'vuex'
export default createStore
({ state: { BISBN : '9787302598503', bookPrice : 99.8, bookAuthor : '刘巍', bookPress : '江西出版社' }, mutations: { }, actions: { }, modules: { }})
main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
11.3.2 Vuex中的getters
在工程项目中,有时需要从 store.state 中派生出一些状态,如对列表进行过滤并计数,可以通过计算属性来实现,具体代码如下。
computed: {doneTodosCount () {return this.$store.state.todos.filter(todo=> todo.done).length}
}
Vuex允许在store中定义“getters”(可以认为是 store 的计算属性)。getters可以接受state 作为其第一个参数,示例代码如下。
const store = createStore({state: {todos: [{ id: 1, text: '...', done: true },{ id: 2, text: '...', done: false }]},getters: {doneTodos (state) {return state.todos.filter(todo => todo.done)}} })
index.js
import { createStore } from 'vuex'
export default createStore({state: {BISBN : '9787302598503',bookPrice : 99.8,bookAuthor : '刘巍',bookPress : '江西出版'},getters: {//接受 state 作为其第一个参数getBookPrice(state) {return state.bookPrice},//接受其他 getters 作为第二个参数getThreeTimesBookPrice(state, getters) {return state.bookPrice + getters.getBookPrice * 2}},mutations: {},actions: {},modules: {}
})
getters程序流程图:
①在21行中用插值语法读取计算属性的值.
②计算梳理有个方法mapGetters里面有数组,数组里面就是方法名,系统就会调用store下面的index.JS文件,getters是默认的语法关键字.
③getters方法就是从数据源state中获取数据,然后二次加工.
app.vue
<template><div><h3>{{bookName}}</h3><h3>作者:{{$store.state.bookAuthor}}</h3><h3>出版社:{{$store.state.bookPress}}</h3><h3>ISBN:{{$store.state.BISBN}}</h3><h3>价格:{{$store.state.bookPrice}}</h3></div><hr/><div><h3>{{bookName}}</h3><h3>作者:{{bookAuthor}}</h3><h3>出版社:{{bookPress}}</h3><h3>ISBN:{{BISBN}}</h3><h3>价格:{{bookPrice}}</h3></div><hr/><h3>getters访问</h3><h3>一本书花的钱:{{$store.getters.getBookPrice}}</h3><h3>三本书花的钱:{{$store.getters.getThreeTimesBookPrice}}</h3><h3>一本书花的钱:{{getBookPrice}}</h3><h3>三本书花的钱:{{getThreeTimesBookPrice}}</h3>
</template>
<script>
import { mapState } from 'vuex'
import { mapGetters } from 'vuex'
export default {name: 'App',data() {//组件中的私有数据return {bookName : 'Vue.js 3'}},//使用计算属性获取store中的状态数据computed: {bookPress() {return this.$store.state.bookPress},//使用对象展开运算符获取store中的状态数据...mapState(['BISBN', 'bookPrice', 'bookAuthor']),...mapGetters(['getBookPrice', 'getThreeTimesBookPrice'])//混入计算属性}
}
</script>
<style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>
main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
11.3.3 Vuex中的mutations
更改 Vuex 的 store 中的状态的唯一方法是提交 mutations。每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数。(其实简单的理解就是state的数据不能直接修改,需要通过mutations里面的方法去修改数据源state的数据)
const store = createStore({state: {count: 1},mutations: {increment (state) { // increment为事件类型type,state为参数// 变更状态state.count++}}})
不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation 处理函数,需要以相应的 type 调用 store.commit()方法。store.commit('increment')
11.3.4 Vuex中的actions
actions 类似于 mutations,不同在于以下两点。
l actions提交的是 mutations,而不是直接变更状态。
l actions可以包含任意异步操作。
actions中的方法需要使用store.dispatch()方法调用。action函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit() 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
一句话解释:action就是异步请求,比如我调用一个接口,我不会等接口结果,就处理后面的代码,等接口返回数据的时候,我重新渲染页面。
app.vue
<template><div><h3>书名:{{bookName}}</h3><h3>出版社:{{$store.state.bookPress}}</h3><h3>作者:{{$store.state.bookAuthor}}</h3><h3>原价:{{bookPrice}}</h3></div><hr/><my-add></my-add><hr/><my-reduce></my-reduce>
</template>
<script>
import { mapState } from 'vuex'
import AddBookPrice from './components/AddBookPrice.vue'
import ReduceBookPrice from './components/ReduceBookPrice.vue'
export default {name: 'App',computed: {...mapState(//混入计算属性['bookName','bookPrice'])},components: {//定义子组件'my-add': AddBookPrice,'my-reduce': ReduceBookPrice}
}
</script>
<style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>
index.js
import { createStore } from 'vuex'
export default createStore({state: {BISBN : '9787302598503',bookPrice : 99.8,bookAuthor : '刘巍',bookPress : '江西出版社',bookName : 'Vue.js 3'},getts: {},mutations: {addBookBy10(state) {state.bookPrice = state.bookPrice + 10},addBookByNum(state, num) {state.bookPrice = state.bookPrice + num},reduceBookBy10(state) {state.bookPrice = state.bookPrice - 10},reduceBookByNum(state, num) {state.bookPrice = state.bookPrice - num},},actions: {//同步增加addBookBy10Action(context) {//执行mutations中的addBookBy10context.commit('addBookBy10')},//同步减少,step为参数reduceBookByNumAction(context, step) {//执行mutations中的reduceBookByNumcontext.commit('reduceBookByNum', step)},//异步增加addBookBy10ActionAsync(context) {setInterval(() => {context.commit('addBookBy10')}, 1000);},//异步减少,step为参数reduceBookByNumActionAsync(context, step) {setInterval(() => {context.commit('reduceBookByNum', step)}, 1000);}},modules: {}
})
main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')