首先,需要清楚为什么要用 vuex ,当我们的应用遇到 多个组件共享状态 时
多层级父子组件状态传递会特别繁琐
非嵌套父子组件状态传递也会特别繁琐

组件数据是层层分发共享的,父子级传递数据有两种方式:
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式,类似 redux

这种状态管理模式包含:
State : 状态数据源
View : 使用状态数据源的视图(不同组件直接从state单向获取数据)
Actions : 修改更新数据源的操作(视图更新数据不能直接更改,必须通过actions更改)
这种模式遵循的是 单向数据流 模式

State : 存储应用状态数据(React 中的 State)
Vue Component : 消费State ,即从State中获取数据
Actions : 提交修改State的动作(包括异步行为,主要负责异步提交)(React 中的 action)
Mutations : 唯一更改 State 的位置(React 中的 Reducer)(真正进行修改操作)
- npm i vuex
- // or
- yarn add vuex
安装报错解决:
- npm ERR! code ERESOLVE
- npm ERR! ERESOLVE unable to resolve dependency tree
- npm ERR!
- npm ERR! While resolving: vuex@0.1.0
- npm ERR! node_modules/vue
- npm ERR! vue@"^2.6.11" from the root project
- npm ERR!
- npm ERR! Could not resolve dependency:
- npm ERR! peer vue@"^3.0.2" from vuex@4.0.2
- npm ERR! node_modules/vuex
- npm ERR! vuex@"4.0.2" from the root project
- npm ERR!
- npm ERR! Fix the upstream dependency conflict, or retry
- npm ERR! this command with --force, or --legacy-peer-deps
- npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
- npm ERR!
- npm ERR! See C:\Users\Administrator\AppData\Local\npm-cache\eresolve-report.txt for a full report.
-
- npm ERR! A complete log of this run can be found in:
- npm ERR! C:\Users\Administrator\AppData\Local\npm-cache\_logs\2022-06-14T09_14_49_535Z-debug-0.log
问题出现原因:
查看项目的package.json文件,查看vue的版本和vuex的版本( vue2 对应vuex3, vue3 对应vuex4) 切记不可弄错,否则就会报错
解决:安装指定版本vuex
npm install vuex@3.6.2 --save
查看package.json文件中是否安装成功

- <script src="vue.js"></script>
- <script src="vuex.js"></script>
通过 <script> 方式引入,vuex 会自动安装(也就是主动调用 Vue.use(Vuex))
import 引入- import Vue from 'vue'
- import Vuex from 'vuex'
-
- Vue.use(Vuex)
通过 import 方式引入,需要手动安装(手动调用 Vue.use(Vuex))
Store 就是仓库,我们前面提到的 state 就存储在 store 中,同时提交动作、修改状态的方法也都由 store 提供和管理
- import Vue from 'vue'
- import Vuex from 'vuex'
-
- Vue.use(Vuex)
-
- let store = new Vuex.Store({
- state: {},
- getters: {},
- mutations: {},
- actions: {}
- })
必须在
Vue.use(Vuex)之后创建store
存储应用状态数据的对象,state 的值可以是一个对象,也可以是一个返回对象的函数,类似 vue 中组件的 data ,使用函数的方式返回对象可以避免对象引用导致的副作用问题(如下例,state值为对象时,返回100,100;state值为返回对象的函数时,返回100,1)
通过 store.state 访问状态数据
state 数据与组件 data 一样是被追踪的
- // state值为对象
- let state = {a: 1};
- // state值为返回对象的函数
- // let state = _=>({ a:1 });
- const store = new Vuex.Store({state});
-
- const store2 = new Vuex.Store({state});
-
- // 如果state为对象true,如果为返回对象的函数则为false
- console.log(store.state == store2.state);
- store.state.a = 100;
- // 使用函数的方式返回对象可以避免对象引用导致的副作用问题:state值为对象时,100,100;state值为返回对象的函数时,100,1
- console.log(store.state.a, store2.state.a);
- // stores/index.js
- import Vue from 'vue';
- import Vuex from 'vuex';
- import state from './state';
-
- Vue.use(Vuex);
-
- const store = new Vuex.Store({
- state
- })
-
- export default store;
- // stores/state.js
- // state为返回函数的对象
- export default () => ({
- title: '商品列表',
- content: '首页汇总'
- })
- <template>
- <div class="home">
- <h2>{{title}}</h2>
- <div>{{content}}</div>
- </div>
- </template>
-
- <script>
- import store from '@/stores'
- export default {
- name: 'home',
- data() {
- return {
- title: store.state.title,
- content: store.state.content
- }
- }
- }
- </script>
问题:
state的更新并不会更新视图
解决 :使用 computed (这种方式也不是特别推荐,推荐使用辅助函数mapState)
- <template>
- <div class="home">
- <h2>{{title}}</h2>
- <div>{{content}}</div>
- </div>
- </template>
-
- <script>
- import store from '@/stores'
-
- store.state.title = "所有商品列表";
-
- export default {
- name: 'home',
- computed: {
- title() {
- return store.state.title
- },
- content() {
- return store.state.content
- }
- }
- }
- </script>
如果每个组件在使用 store 的时候都 import 会比较繁琐,这个时候,我们通过 vuex 提供的 store 选项把 store 对象注入到 vue 的原型上
- import Vue from 'vue'
- import App from './App.vue'
- import router from './router'
- import store from '@/stores'
-
- Vue.config.productionTip = false
-
- new Vue({
- router,
- store,
- render: h => h(App)
- }).$mount('#app')
配置注入后,我们就可以在组件实例中使用 this.$store 来访问 store 对象了
注意因为注入到实例vue上了,所以,在export以外this为undefined,所以不能在其范围外更改store里的state值了,如下例this.$store.state.title = "所有商品列表";可以在created方法中使用
- <template>
- <div class="home">
- <h2>{{title}}</h2>
- <div>{{content}}</div>
- </div>
- </template>
-
- <script>
- // import store from '@/stores' // 可以去掉了
-
- // 注意在组件内部才能使用this,所以不能在这里使用this.$store
- // this.$store.state.title = "所有商品列表";
-
- export default {
- name: 'home',
- created() {
- this.$store.state.title = "所有商品列表";
- },
- computed: {
- title() {
- return this.$store.state.title
- },
- content() {
- return this.$store.state.content
- }
- }
- }
- </script>
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键,通常我们把 store 的 state 通过 mapState 函数映射到组件的 computed 上
1.mapState中使用数组
- <template>
- <div class="home">
- <h2>{{title}}</h2>
- <div>{{content}}</div>
- </div>
- </template>
-
- <script>
- import {mapState} from 'vuex'
-
- export default {
- name: 'home',
- created() {
- this.$store.state.title = "所有商品列表";
- },
- computed: mapState([
- 'title','content'
- ])
- }
- </script>
2.mapState中使用对象:通过对象方式进行映射
场景:当组件中已有与
store同名的数据名称(当组件data中已有与 `store` 同名的数据名称时,需要使用对象重新定义属性名)
- <template>
- <div class="home">
- <h1>{{title}}</h1>
- <h2>{{subTitle}}</h2>
- <div>{{content}}</div>
- </div>
- </template>
-
- <script>
- import {mapState} from 'vuex'
-
- export default {
- name: 'home',
- data() {
- return {title: 'Vuex'}
- },
- computed: mapState({
- subTitle: 'title',
- content: ({content}) => content.length <= 12 ? content : content.substring(0, 12) + '......'
- })
- }
- </script>
3.mapState中使用对象函数,如上例的content
mapState 返回的 state 属性与组件已有计算属性进行融合通过对象扩展运算符,可以把 mapState 返回的 state 属性与组件已有计算属性进行融合。融合后只有一个title值
- <script>
- import {mapState} from 'vuex'
-
- export default {
- computed:{
- ...mapState({
- subTitle:'title',
- content: 'content'
- })
- },
- }
- </script>
有时候我们需要从 store 中的 state 中派生出一些状态,类似组件的 data 与 computed,store 也提供了一个 getters 对象来处理派生数据
与组件属性一样,我们是通过定义一个函数的形式来返回派生数据的,getters 函数接受两个参数
第一个参数:state 对象
第二个参数:getters 对象
Getter 也可以接受其他 getter 作为第二个参数:
- getters: {
- // ...
- doneTodosCount (state, getters) {
- return getters.doneTodos.length
- }
- }
store.getters.doneTodosCount
同样的,与组件计算属性一样,默认是通过属性的方式去访问 getters 中的数据的,这种方式与组件的计算属性一样,也是会缓存结果的
我们还可以通过闭包函数的形式返回一个函数,来实现给 getters 函数传参,需要注意的是这种方式不支持结果缓存
- //Home.vue
- <template>
- ...
- <button @click="type='Apple'">显示Apple</button>
- <button @click="type='XiaoMi'">显示XiaoMi</button>
- <button @click="type='HuaWei'">显示HuaWei</button>
- </template>
- <script>
- ...
- data() {
- return {
- ...
- type: '',
- ...
- };
- },
- computed:{
- ...
- items(){
- return this.$store.getters.showItems(this.type);
- }
- },
- </script>
- //store.js
- ...
- getters: {
- showItems(state) {
- return function(type) {
- if (type === '')
- return state.items;
- return state.items.filter(item => {
- return item.vendor == type;
- })
- };
- }
- },
与 mapState 函数类似,通常映射到组件的 computed 上
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation(类似 redux 中的 action + reducer),Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)
mutation中的函数不要直接调用
type 要提交的 mutation 回调函数名称
payload 载荷:提交的额外数据,任意格式
- store.commit(type, payload)
- // or
- store.commit({
- type: ...,
- payload: ...
- })
mutation 中的函数被 commit 执行的时候,接收两个参数
第一个参数:state 对象
第二个参数: commit 提交的 payload
在 mutation 函数中,我们就可以通过 state 对象进行状态数据的修改
mapMutations 函数的使用与 mapState 和 mapGetters 类似,但是其一般是把组件的 methods 映射为 store 的 mutations 的 commit 调用
commit 方法没有返回值
action 中的函数与 mutation 中的函数类似,但是它主要用来进行异步任务的处理,然后通过提交 mutation 来修改 state
注意:
action中的函数不要直接修改state
- store.dispatch(type, payload)
- // or
- store.dispatch({
- type: ...,
- payload: ...
- })
action 任务需要通过 dispatch 方法来提交(派发),与 commit 类似
dispatch 方法有返回值,且一定返回一个 promise 对象
action 中的函数执行的过程中也接受两个参数
第一个参数:store 对象
第二个参数: dispatch 提交的 payload
与 mapMutations 函数类似,把组件的 methods 映射为 store 的 actions 的 dispatch 调用
1:创建一个新的项目,首页显示所有商品名称,点击名称进入商品详情页
2:商品详情页需要获取商品信息,而所有的商品信息在首页都已经获取过,通过父组件传递数据到子页面(商品详情页)可以实现,但是当数据特别复杂,子页面特别多时会不好处理
3:将store注入vue实例,通过store管理数据,并传递给商品详情页
4:直接修改store仓库中的state数据,vue视图不会立即改变。示例:通过edit按钮点击测试直接修改state数据,页面的变化
5:页面不会刷新,解决方案:在computed中更改state数据
6:当一个组件需要获取多个状态即state数据时,如果将这些数据都声明为computed计算属性,就会繁琐和冗余。为此,可以通过mapState辅助函数帮助生成计算属性
7:mapState计算属性的三种用法(使用数组,使用对象,使用函数),且当vuex的计算函数和vue自身的computed重合时,使用扩展运算符进行融合
8:有时需要从Store中的State中派生出一些数据(类似组件的data和computed属性的关系),此时Store提供了getters对象来处理派生数据。案例:分类显示商品
9:getters函数的三种访问方式(属性、方法、辅助函数mapGetters)
10:mutations函数:用于提交修改Store中的数据状态,可以提交数据但是没有数据返回。案例:新增商品信息页面(保证后端各接口可用)。
11:actions函数:可以异步提交数据,有返回数据,返回值包装在Promise对象中。 案例:新增商品信息页面,并获取返回值
12:串通组件、mutations、actions三者实现新增商品信息。提交过程:在created方法中发起dispatch请求,请求到actions中的commit()发起后端请求,然后调用mutations的commit()更改仓库数据
13:怎么更新视图?Add组件中点击添加,调用异步方法addItem(),此方法中通过this.$store.dispatch()方法调用actions()中的addItem(),actions()中的addItem()发起请求后端处理数据,当成功返回后,通过commit('addItem', payload)调用mutations方法,mutations方法只处理store相关的操作,即改变state数据状态;
14:商品信息展示页面也使用store而不直接请求并显示:首次进入页面,通过created()调用commit(),调用actions中的getItems()发起后端请求获取所有商品数据信息,成功返回后,通过mutations中的updateItems()方法更新state数据状态
15.组件调用时,如果直接调用mutations发起请求,需要使用commit方法:this.$store.commit('getItems');如果调用action方法发起请求需要使用dispatch方法this.$store.dispatch('getItems');
案例代码:
此案例使用vue3实现,在router配置和main.js实例注入方式上会有些不一样的地方:
- //main.js
- import { createApp } from 'vue'
- import App from './App.vue'
-
- import Router from '@/router/router.js'
- import Store from '@/store/store.js'
-
- createApp(App).use(Router).use(Store).mount('#app')
- //App.vue
- <template>
- <div id="app">
- <h1>vuex</h1>
- <div id="nav">
- <router-link to="/">首页</router-link>
- </div>
- <router-view></router-view>
- </div>
- </template>
-
- <script>
-
- export default {
- name: 'App',
- components: {
- }
- }
- </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>
- //router.js
- // 注意vue3和vue2引入方式和写法不同
- import { createRouter, createWebHistory } from "vue-router";
-
- const vuexRouter = createRouter({
- history: createWebHistory(),
- routes: [{
- name: 'home',
- path: '/',
- title: '首页',
- // 注意vue3一定要用这种加载组件方式component:Home这种会报错
- component: () =>
- import ('@/views/Home.vue'),
- },
- {
- name: 'item',
- path: '/item/:itemId',
- title: '商品详情页',
- props: true,
- component: () =>
- import ('@/views/Item.vue'),
- },
- {
- path: '/*',
- title: '404',
- // 注意vue3一定要用这种加载组件方式component:Home这种会报错
- component: () =>
- import ('@/views/NotFound.vue'),
- },
- ]
-
- });
-
- export default vuexRouter;
- //Home.vue
- <template>
- <div class="home">
- <hr />
- <button @click="edit">清空store,测试视图是否改变?</button>
- <br>
- <button @click="addItem">新增商品</button>
- <input type="text" ref="name"/>
- <div id="mesg" v-show="mesg">*商品名称错误*</div>
- <hr />
- <br />
- <button @click="type='Apple'">显示Apple</button>
- <button @click="type='XiaoMi'">显示XiaoMi</button>
- <button @click="type='HuaWei'">显示HuaWei</button>
- <div v-if="success">
- <ul>
- <li v-for="item in items" :key="item.id">
- <router-link :to="{name:'item',params:{itemId:item.id}}">{{item.name}}</router-link>
- </li>
- </ul>
- </div>
- <div v-else>
- <NotFound />
- </div>
- </div>
- </template>
-
- <script>
- // import axios from "axios";
- import NotFound from "@/views/NotFound.vue";
- import {mapState} from 'vuex';
-
- export default {
- name: "Home",
- components: {
- NotFound
- },
- data() {
- return {
- // 使用了store中的items,这里就不用注册了
- // items: this.$store.state.items,
- // itemId: 0,
- success: "",
- type: '',
- // 错误信息显示
- mesg:false
- };
- },
- async created() {
- console.log("created------");
- // 通过mutations发起请求用commit,通过action发起请求用dispatch
- // this.$store.commit('getItems');
- //不再直接发起请求,而是通过store的action发起请求
- // 因为需要请求并获取数据后,得到success再渲染,所以使用异步
- await this.$store.dispatch('getItems');
- this.success = (Object.keys(this.$store.state.items).length === 0 ? false:true);
-
- },
- // 直接更改store,视图不会改变,可以在computed中监听修改后立即改变视图。计算属性监听items数据变化
- // 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 `mapState` 辅助函数帮助我们生成计算属性
- computed:{
- success(){
- return this.success;
- },
- items(){
- // return this.$store.state.items;
- // 通过getters获取派生数据
- return this.$store.getters.showItems(this.type);
- }
- },
- methods: {
- edit() {
- // 发现直接修改store中的数据,视图不会更改(虽然也可以直接更改store数据然后通过computed监听,或者html中直接使用this.$store.state.items进行渲染,但是不建议如此使用)
- // console.log(this.$store.state.items);
- this.$store.state.items = [];
- // console.log(this.$store.state.items);
-
- },
- async addItem(){
- // 带上数据itemName(通过ref属性获取)
- let name = this.$refs.name.value;
- if(name===''){
- this.mesg = true;
- return;
- }
-
- await this.$store.dispatch('addItems',name);
- // 清空input
- this.$refs.name.value = '';
- }
- }
- };
- </script>
- <style scoped>
- ul {
- margin: 0 0;
- }
- li {
- list-style-type: none;
- padding: 4px 4px;
- }
- #mesg {
- color:red;
- }
- </style>
- //Item.vue
- <template>
- <div><hr>{{this.item.name}}</div>
- </template>
- <script>
- import axios from "axios";
- export default {
- // 从仓库中通过itemId取数据
- name: "Item",
- //通过组件props传参,需要在路由中设置props:true
- props: ["itemId"],
- data() {
- return {
- item: null
- };
- },
- created() {
- //从store中筛选数据
- let itemData = this.$store.state.items.filter(
- item => item.id == this.itemId
- );
- // 获取的数据是Proxy {0: Proxy}对象,需要从里面解析出数据
- this.item = JSON.parse(JSON.stringify(itemData))[0];
- }
- };
- </script>
- <template>
- <div>
- 错误-----未找到商品信息!!!
- </div>
- </template>
- //store.js 用于处理vuex store相关操作
- import Vuex from 'vuex'
- import axios from "axios";
-
- const store = new Vuex.Store({
- state: {
- // 写死数据,测试getters派生数据(类似组件的data与computed的关系)
- // items: [{ "id": 3, "name": "Macbook Pro 15.4", "vendor": "Apple", "price": 1949900 }, { "id": 4, "name": "Apple iMac", "vendor": "Apple", "price": 1629900 }, { "id": 9, "name": "游戏本2019款", "vendor": "XiaoMi", "price": 879900 }, { "id": 6, "name": "Apple Watch Series 4", "vendor": "Apple", "price": 599900 }, { "id": 1, "name": "iPhone XR", "vendor": "Apple", "price": 542500 }, { "id": 11, "name": "HUAWEI P30 Pro", "vendor": "HuaWei", "price": 498800 }, { "id": 2, "name": "Apple iPad Air 3", "vendor": "Apple", "price": 377700 }, { "id": 10, "name": "HUAWEI P30", "vendor": "HuaWei", "price": 368800 }, { "id": 7, "name": "小米9", "vendor": "XiaoMi", "price": 259900 }, { "id": 12, "name": "华为平板 M6 10.8英寸", "vendor": "HuaWei", "price": 229900 }, { "id": 16, "name": "Redmi K20", "vendor": "XiaoMi", "price": 199900 }, { "id": 13, "name": "HUAWEI WATCH GT", "vendor": "HuaWei", "price": 128800 }, { "id": 5, "name": "Apple Magic Mouse", "vendor": "Apple", "price": 72900 }, { "id": 8, "name": "小米手环4", "vendor": "XiaoMi", "price": 16900 }],
- // 通过请求获取数据放入仓库时就不需要写死
- items: []
- },
- getters: {
- // Getter 也可以接受其他 getter 作为第二个参数:
- showItems(state) {
- // 在此方法中根据type显示数据
- // 注意如果以函数方式调用,则getters中的方法返回值也需要是一个函数,参数为调用时传入的参数this.type
- return function(type) {
- if (type === '')
- return state.items;
- return state.items.filter(item => {
- return item.vendor == type;
- })
- };
- }
- },
- mutations: {
- // 只负责和Store有关操作。两个参数分别为store中state数据和请求数据
- updateItems(state, payload) {
- console.log("mutations------");
- console.log(payload);
-
- // 将数据存放进仓库。
- state.items = payload;
- },
- addItems(state, payload) {
- console.log("mutations------addItems");
- console.log(payload);
- state.items.unshift(payload);
- }
- },
- actions: {
- // 在actions中提交请求,在mutations中更改仓库数据
- async getItems(context) {
- console.log("actions------");
- // 发起请求
- await axios({
- url: "/api/items"
- })
- .then(res => {
- // 参数分别为mutations的方法和需要放进仓库的数据
- if (res.status === 200) {
- context.commit('updateItems', res.data);
- }
- })
- .catch(err => {
- console.log(err);
- });
-
- },
- async addItems(context, name) {
- console.log("actions------addItems" + name);
- // 发起请求
- await axios({
- url: "/api/addItem",
- method: 'post',
- // 数据
- data: {
- name
- }
- })
- .then(res => {
- console.log(res);
- // 参数分别为mutations的方法和需要放进仓库的数据
- if (res.status === 200) {
- context.commit('addItems', res.data);
- }
- })
- .catch(err => {
- console.log(err);
- });
- }
- }
- });
-
- export default store;
后端请求接口(node.js简单实现):
- //app.js
- const Koa = require('koa');
- const KoaRouter = require('koa-router');
- //接收post参数,需要使用koa-bodyparser模块
- const bodyParser = require("koa-bodyparser");
-
- let datas = {
- items: require('./data/items.json'),
- users: require('./data/users.json')
- }
- // console.log(datas);
-
- const app = new Koa();
-
- const router = new KoaRouter();
-
- // 新增商品id
- let currentId = 20;
-
- router.get('/', async ctx => {
- ctx.body = 'api';
- });
-
- router.get('/items', async ctx => {
-
- let sort = ctx.request.query.sort || 'desc';
- let items = datas.items.sort((a, b) => sort === 'asc' ? a.price - b.price : b.price - a.price);
-
- ctx.body = items;
- });
-
- router.get('/item/:id', async ctx => {
- let id = Number(ctx.params.id);
- let item = datas.items.find(item => item.id === id);
-
- if (!item) {
- // return ctx.body = {
- // code: 1,
- // message: '没有该商品的信息'
- // }
- ctx.throw(404, '没有该商品信息');
- return;
- }
- ctx.body = item;
- });
-
- // 新增商品
- router.post('/addItem', async ctx => {
- let item = ctx.request.body;
-
- if (item.name != '') {
- currentId++;
- item.id = currentId;
- item.vendor = 'Apple';
- item.price = 251205;
- datas.items.unshift(item);
- console.log(datas.items);
- }
- // 返回item数据用于存储在store中
- ctx.body = item;
- });
-
- app.use(bodyParser());
- app.use(router.routes());
-
- app.listen(7777);
这个更多的是基于一种代码组织结构上的辅助
https://vuex.vuejs.org/zh/guide/modules.html