Vue全家桶-Vue-router&Vuex

166 阅读5分钟

Vue全家桶-Vue-router&Vuex

Vue-Router

资料

介绍

Vue Router 是 Vue.js 官⽅的路由管理器。它和 Vue.js 的核⼼深度集成,让构建单⻚⾯应⽤变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有⾃动激活的 CSS class 的链接
  • HTML5 history模式或 hash 模式,在 IE9 中⾃动降级
  • ⾃定义的滚动条⾏为

起步

⽤ Vue.js + Vue Router 创建单⻚应⽤,是⾮常简单的。使⽤ Vue.js,我们已经可以通过组合组件来组成应⽤程序,当你要把 Vue Router添加进来,我们需要做的是,将组件 (components) 映射到路由(routes),然后告诉 Vue Router 在哪⾥渲染它们

安装

npm i vue-router -S

main.js

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

推荐使⽤:vue add router 添加插件(记得提前提交)

基本使用

router.js

  import Vue from 'vue'
        //1.导⼊
        import Router from 'vue-router'
        import Home from './views/Home.vue'
        import About from './views/About.vue'
        //2.模块化机制 使⽤Router
        Vue.use(Router)
        //3.创建路由器对象
        const router = new Router({
            routes: [{
                path: '/home',
                component: Home
            },
            {
                path: '/about',
                component: About
            }
            ]
        })
   export default router;

做好以上配置后

App.vue

<template>
        <div id="app">
            <div id="nav">
                <!-- 使⽤router-link组件来导航 -->
                <!-- 通过传⼊to属性指定连接 -->
                <!-- router-link默认会被渲染成⼀个a标签 -->
                <router-link to="/">Home</router-link> |
                <router-link to="/about">About</router-link> |
            </div>
            <!-- 路由出⼝ -->
            <!-- 路由匹配的组件将被渲染到这⾥ -->
            <router-view />
        </div>
</template>

打开浏览器.,切换Home和About超链接,查看效果

在配置路由的时候,给路由添加名字,访问时就可以动态的根据名字来进⾏访问

 const router = new Router({
            routes: [{
                path: '/home',
                name: "home",
                component: Home
            },
            {
                path: '/about',
                name: 'about',
                component: About
            }]
 })

要链接到⼀个命名路由,可以给 router-link 的 to 属性传⼀个对象:

<router-link :to="{name:'home'}">Home</router-link> |
<router-link :to="{name:'about'}">About</router-link> |

动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有⼀个 User 组件,对于所有 ID 各不相同的⽤户,都要使⽤这个组件来渲染。那么,我们可以在vue-router 的路由路径中使⽤“动态路径参数”(dynamic segment) 来达到这个效果

User.vue

 <template>
        <div>
            <h3>⽤户⻚⾯</h3>
        </div>
    </template>
    <script>
        export default {
        };
    </script>
    <style lang="scss" scoped>

    </style>

路由配置

 const router = new Router({
            routes: [
                {
                    path: '/user/:id', name: 'user', component: User,
                },
            ]
        })
<router-link :to="{name:'user',params:
{id:1}}">User</router-link> |

访问

http://localhost:8080/user/1

http://localhost:8080/user/2

查看效果

当匹配到路由时,参数值会被设置到this.$route.params,可以在每个组件中使⽤,于是,我们可以更新 User 的模板,输出当前⽤户的 ID:

 <template>
        <div>
            <h3>⽤户⻚⾯{{$route.params.id}}</h3>
        </div>
    </template>

响应路由参数的变化

提醒⼀下,当使⽤路由参数时,例如从 /user/1 导航到 /user/2原来的组件实例会被复用。因为两个路由都渲染同个组件,⽐起销毁再创建,复⽤则显得更加⾼效。不过,这也意味着组件的生命周期钩子不会再被调⽤

复⽤组件时,想对路由参数的变化作出响应的话,你可以简单地watch (监测变化) $route 对象:

/*使⽤watch(监测变化) $route对象
 watch: {
 $route(to, from) {
 console.log(to.params.id);
 }
 }, */
// 或者使⽤导航守卫
beforeRouteUpdate(to,from,next){
 //查看路由的变化
 //⼀定要调⽤next,不然就会阻塞路由的变化
 next();
}

404路由

	const router = new Router({
            routes: [
                //....
                // 匹配不到理由时,404⻚⾯显示
                {
                    path: '*',
                    component: () =>import('@/views/404')
                }
            ]
        })

当使⽤通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常⽤于客户端 404错误

当使⽤⼀个通配符时, $route.params 内会⾃动添加⼀个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:

 	{
            path: '/user-*',
            component: () => import('@/views/User-admin.vue')
        }
        this.$route.params.pathMatch // 'admin'

匹配优先级

有时候,同⼀个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最⾼。

查询参数

类似像地址上出现的这种:http://localhos:8080/page?id=1&title=foo

  	const router = new Router({
            routes: [
                //....
                {
                    name: '/page',
                    name: 'page',
                    component: () => import('@/views/Page.vue')
                }

            ]
        })
<router-link :to="{name:'page',query:
{id:1,title:'foo'}}">User</router-link> |

访问http://localhos:8080/page?id=1&title=foo查看Page.vue

	<template>
        <div>
            <h3>Page⻚⾯</h3>
            <h3>{{$route.query.userId}}</h3>
        </div>
    </template>
    <script>
        export default {
            created() {
                //查看路由信息对象
                console.log(this.$route);
            },
        }
    </script>
    <style lang="scss" scoped>
    </style>

路由重定向和别名

例⼦是从 / 重定向到 /home :

	const router = new Router({
            mode: 'history',
            routes: [
                // 重定向
                {
                    path: '/',
                    redirect: '/home'
                },
                {
                    path: '/home',
                    name: 'home',
                    component: Home
                },
            ]
        })

重定向的⽬标也可以是⼀个命名的路由:

const router = new VueRouter({
 routes: [
 { path: '/', redirect: { name: 'name' }}
 ]
})

别名

{
     path: '/user/:id',
     name: 'user',
     component: User,
     alias: '/alias'
}

起别名,仅仅起起别名 ⽤户访问http://loacalhost:8080/alias的时候, 显示User组件

别名”的功能让你可以⾃由地将 UI 结构映射到任意的 URL,⽽不是受限于配置的嵌套路由结构。

路由组件传参

在组件中使⽤ $route 会使之与其对应路由形成⾼度耦合,从⽽使组件只能在某些特定的 URL 上使⽤,限制了其灵活性。

使⽤ props 将组件和路由解耦

取代与 $route的耦合

 {
     path: '/user/:id',
     name: 'user', 
     component: User,  
     props:true
 }

User.vue

<template>
	<div>  
		<h3>⽤户⻚⾯{{$route.params.id}}</h3> 
		<h3>⽤户⻚⾯{{id}}</h3> 
	</div>  
</template>  
<script>  
	export default{  
        //....
         props: {
         		id: {
         		type: String,
        		default: ''
         		},
         },
    }
 </script>

props也可以是个函数

{  
    path: '/user/:id',  
    name: 'user',  
    component: User,  
    props: (route)=>({
    	id: route.params.id,
    	title:route.query.title	
 	})
}

User.vue

 <template>
        <div>
            <h3>⽤户⻚⾯{{id}}-{{title}}</h3>
        </div>
    </template>
    <script>
        export default {
            // ...e
            props: {
                id: {
                    type: String,
                    default: ''
                },
                title: {
                    type: String
                }
            },
        };
    </script>

编程式导航

除了使⽤ 创建 a 标签来定义导航链接,我们还可以借助 router 的实例⽅法,通过编写代码来实现。

注意Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调⽤ this.$router.push

声明式编程式
router.push(...)

该⽅法的参数可以是⼀个字符串路径,或者⼀个描述地址的对象。例如

 // 字符串
        this.$router.push('home')
        // 对象
        this.$router.push({ path: 'home' })
        // 命名的路由
        this.$router.push({
            name: 'user', params: {
                userId:'123'
            }
        })
        // 带查询参数,变成 /register?plan=private
        this.$.push({
            path: 'register', 
            query: {
                plan:'private'
            }
        })

前进后退

 <script>
        // 在浏览器记录中前进⼀步,等同于 history.forward()
        router.go(1)
        // 后退⼀步记录,等同于 history.back()
        router.go(-1)
        // 前进 3 步记录
        router.go(3)
        // 如果 history 记录不够⽤,那就默默地失败
        router.go(-100)
        router.go(100)
 </script>

嵌套路由

实际⽣活中的应⽤界⾯,通常由多层嵌套的组件组合⽽成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件

/user/1/profile            	/user/1/posts
+------------------+       		+-----------------
+
| User 				| 			| User 
|
| +--------------+ | 			| +-------------+
|| | Profile | | +------------> | | Posts 		| 
|
| | 			| |				 | | 			|
| +------------------+ |         | +-----------------+
| +--------------+ | 			 | +-------------+
|
+------------------+ 			 +-----------------
+

router.js

	{
            path: '/user/:id',
            name: 'user',
            component: User,
            props: ({ params, query }) => ({
                id: params.id, title: query.title
            }),
            children: [
                // 当 /user/:id/profile 匹配成功,
                // Profile 会被渲染在 User 的 <router-view> 中
                {
                    path: "profile",
                    component: Profile
                },
                // 当 /user/:id/posts 匹配成功,
                // Posts 会被渲染在 User 的 <router-view> 中
                {
                    path: "posts",
                    component: Posts
                }
            ]
        }

在 User 组件的模板添加⼀个 :

   <template>
        <div>
            <h3>⽤户⻚⾯{{$route.params.id}}</h3>
            <h3>⽤户⻚⾯{{id}}</h3>
            <router-view></router-view>
        </div>
    </template>

App.vue

    <template>
        <div id='app'>
            <!-- 嵌套理由 -->
            <router-link to="/user/1/profile">User/profile</router-link> |
            <router-link to="/user/1/posts">User/posts</router-link> |
        </div>
    </template>

命名视图

有时候想同时 (同级) 展示多个视图,⽽不是嵌套展示,例如创建⼀个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上⽤场了

	{
            path: '/home',
            name: 'home',
            //注意这个key是components
            components: {
                default: Home, //默认的名字
                main: () => import('@/views/Main.vue'),
                sidebar: () => import('@/views/Sidebar.vue')
            }
        }

App.vue

<router-view/>
<router-view name='main'/>
<router-view name='sidebar'/>

导航守卫

导航表示路由正在发⽣改变。

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件⾥调⽤离开守卫。
  3. 调⽤全局的 beforeEach 守卫。
  4. 在重⽤的组件⾥调⽤ beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置⾥调⽤ beforeEnter 。
  6. 解析异步路由组件。
  7. 在被激活的组件⾥调⽤ beforeRouteEnter 。
  8. 调⽤全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调⽤全局的 afterEach 钩⼦。
  11. 触发 DOM 更新。
  12. ⽤创建好的实例调⽤ beforeRouteEnter 守卫中传给 next 的回

全局守卫

你可以使⽤ router.beforeEach 注册⼀个全局前置守卫

const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
 // ...
})

有个需求,⽤户访问在浏览⽹站时,会访问很多组件,当⽤户跳转到 /notes ,发现⽤户没有登录,此时应该让⽤户登录才能查看,应该让⽤户跳转到登录⻚⾯,登录完成之后才可以查看我的笔记的内容,这个时候全局守卫起到了关键的作⽤

有两个路由 /notes 和 /login

router.vue

 	const router = new VueRouter({
            routes: [
                {
                    path: '/notes',
                    name: 'notes',
                    component: () => import('@/views/Notes')
                },
                {
                    path: "/login",
                    name: "login",
                    component: () => import('@/views/Login')
                },]
        })

        // 全局守卫
        router.beforeEach((to, from, next) => {
            //⽤户访问的是'/notes'
            if (to.path === '/notes') {
                //查看⼀下⽤户是否保存了登录状态信息
                let user =
                    ON.parse(localStorage.getItem('user'))
                if (user) {
                    //如果有,直接放⾏
                    next();
                } else {
                    //如果没有,⽤户跳转登录⻚⾯登录
                    next('/login')
                }
            } else {
                next();
            }
        })

Login.vue

    <template>
        <div>
            <input type="text" v-model="username">
            <input type="password" v-model="pwd">
            <button@click="handleLogin">提交</button>
        </div>
    </template>
    <script>
        export default {
            data() {
                return {
                    username: "",
                    pwd: ""
                };
            },
            methods: {
                handleLogin() {
                    // 1.获取⽤户名和密码
                    // 2.与后端发⽣交互
                    setTimeout(() => {
                        let data = {
                            username: this.username
                        };
                        //保存⽤户登录信息
                        localStorage.setItem("user",
                            ON.stringify(data));
                        // 跳转我的笔记⻚⾯
                        this.$router.push({ name: "notes" });
                    }, 1000);
                },

            }
        };
    </script>

App.vue

 	<!-- 全局守卫演示 -->
    <router-link to="/notes">我的笔记</router-link> |
    <router-link to="/login">登录</router-link> |
    <button @click="handleLogout">退出</button>
     export default {
            methods: {
                handleLogout() {
                    //删除登录状态信息
                    localStorage.removeItem("user");
                    //跳转到⾸⻚
                    this.$router.push('/')
                }
            },
        }

组件内的守卫

你可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
   <template>
        <div>
            <h3>⽤户编辑⻚⾯</h3>
            <textarea name id cols="30" rows="10" vmodel="content"></textarea>
            <button @click="saveData">保存</button>
            <div class="wrap" v-for="(item,index) in list" :key="index">
                <p>{{item.title}}</p>
            </div>
        </div>
    </template>
    <script>
        export default {
            data() {
                return {
                    content: "",
                    list: [],
                    confir: true
                };
            },
            methods: {
                saveData() {
                    this.list.push({
                        title: this.content
                    });
                    this.content = "";
                }
            },
            beforeRouteLeave(to, from, next) {
                // 导航离开该组件的对应路由时调⽤
                // 可以访问组件实例 `this`
                if (this.content) {
                    alert("请确保保存信息之后,再离开");
                    next(false);
                } else {
                    next();
                }
            }
        };
    </script>

路由元信息实现权限控制

给需要添加权限的路由设置meta字段

 	{
            path: '/blog',
            name: 'blog', component: () => import('@/views/Blog'),
            meta: {
                requiresAuth: true
            }
        },
        {
            // 路由独享的守卫
            path: '/notes',
            name: 'notes',
            component: () => import('@/views/Notes'),
            meta: {
                requiresAuth: true
            }
        },
         router.beforeEach((to, from, next) => {
            if (to.matched.some(record =>
                cord.meta.requiresAuth)) {
                // 需要权限
                if (!localStorage.getItem('user')) {
                    next({
                        path: '/login', query: {
                            redirect: to.fullPath
                        }
                    })
                } else {
                    next();
                }

            } else {
                next();
            }
        })

login.vue

		handleLogin() {
            // 1.获取⽤户名和密码
            // 2.与后端发⽣交互
            setTimeout(() => {
                let data = {
                    username: this.username
                };
                localStorage.setItem("user",
                    JSON.stringify(data));
                // 跳转到之前的⻚⾯
                this.$router.push({
                    path:
                        this.$route.query.redirect
                });
            }, 1000);
        }

数据获取

有时候,进⼊某个路由后,需要从服务器获取数据。例如,在渲染⽤户信息时,你需要从服务器获取⽤户的数据。我们可以通过两种⽅式来实现:

  • 导航完成之后获取:先完成导航,然后在接下来的组件⽣命周期钩⼦中获取数据。在数据获取期间显示“加载中”之类的指示。
  • 导航完成之前获取:导航完成前,在路由进⼊的守卫中获取数据,在数据获取成功后执⾏导航。
导航完成后的数据获取

当你使⽤这种⽅式时,我们会⻢上导航和渲染组件,然后在组件的 created 钩⼦中获取数据。这让我们有机会在数据获取期间展示⼀个 loading 状态,还可以在不同视图间展示不同的 loading 状态。

	<template>
        <div class="post">
            <div v-if="loading" class="loading">Loading...
            </div>
            <div v-if="error" class="error">{{ error }}</div>
            <div v-if="post" class="content">
                <h2>{{ post.title }}</h2> 9 <p>{{ post.body }}</p>
            </div>
        </div>
    </template>
    <script>
        export default {
            name: "Post", data() {
                return {
                    loading: false, post: null, error: null
                };
            },
            // 组件创建完后获取数据,
            // 此时 data 已经被 监视 了
            created() {
                // 如果路由有变化,会再次执⾏该⽅法
                this.fetchData();
            },
            watch: {
                $route: "fetchData"
            },
            methods: {
                fetchData() {
                    this.error = this.post = null;
                    this.loading = true;
                    this.$http.get('/api/post')
                        .then((result) => {
                            this.loading = false;
                            this.post = result.data;
                        }).catch((err) => {
                            this.error = err.toString();
                        });
                }
            }
        };
    </script>

vuex

Vuex 是⼀个专为 Vue.js 应⽤程序开发的状态管理模式。它采⽤集中式存储管理应⽤的所有组件的状态,并以相应的规则保证状态以⼀种可预测的⽅式发⽣变化。

image-20220121135343865.png

安装vuex

vue add vuex

store.js

   	import Vue from 'vue'
        import Vuex from 'vuex'
        //确保开头调⽤Vue.use(Vuex)
        Vue.use(Vuex)
        export default new Vuex.Store({
            state: { //this.$store.state.count
                count: 0
            },
            getters: {
                evenOrOdd: (state) => {
                    //this.$store.getters.evenOrOdd
                    return state.count % 2 === 0 ? '偶数' : '奇数'
                }
            },
            mutations: {
                increment(state) { //this.$store.commit('increment')
                    state.count++
                },
                decrement(state) { //this.$store.commit('decrement')
                    state.count--
                }
            },
            actions: {
                increment({ commit }) {
                    this.$store.dispatch('increment')
                    // 修改状态的唯⼀⽅式是提交mutation
                    commit('increment');
                },
                decrement({ commit }) {
                    this.$store.dispatch('decrement')
                    commit('decrement');
                },
                incrementAsync({ commit }) {
                    this.$store.dispatch('incrementAsync')
                    return new Promise((resolve, reject) => {
                        setTimeout(() => {
                            commit('increment');
                            resolve(10);
                        }, 1000);
                    })
                }
            }
        })

我们可以在组件的某个合适的时机通过 this.store.state来获取状态对象,以及通过this.store.state 来获取状态对象,以及通过 this.store.commit ⽅法触犯状态变更

this.$store.commit('increment');

mapState辅助函数

当⼀个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使⽤ mapState 辅助函数帮助我们⽣成计算属性,让你少按⼏次键

 // 在单独构建的版本中辅助函数为 Vuex.mapState
        import { mapState } from 'vuex'
        export default {
            // ...
            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'
        ])

对象展开运算符

mapState 函数返回的是⼀个对象。我们如何将它与局部计算属性混合使⽤呢?通常,我们需要使⽤⼀个⼯具函数将多个对象合并为⼀个,以使我们可以将最终对象传给 computed 属性。但是⾃从有了对象展开运算符,极⼤地简化写法

computed:{
 ...mapState({
 "count"
 })
}

mapGetters辅助函数

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

		import { mapGetters } from 'vuex'
        export default {
            // ...
            computed: {
                ...mapGetters([
                    'evenOrOdd'
                ])
            },
        }

如果你想将⼀个 getter 属性另取⼀个名字,使⽤对象形式:

  mapGetters({
            //`this.doneEvenOrOdd` 映射为`this.$store.getters.evenOrOdd`
            doneEvenOrOdd: 'evenOrOdd'
        })

Mutation

更改 Vuex 的 store 中的状态的唯⼀⽅法是提交 mutation。Vuex 中 的 mutation ⾮常类似于事件:每个 mutation 都有⼀个字符串的 事件类型 (type) 和 ⼀个 回调函数 (handler)。这个回调函数就是我们实际进⾏状态更改的地⽅,并且它会接受 state 作为第⼀个参数:

MapMutation

你可以在组件中使⽤ this.$store.commit('xxx') 提交 mutation,或者使⽤ mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调⽤(需要在根节点注⼊ store )。

 	import { mapMutations } from 'vuex'
        export default {
            // ...
            methods: {
                ...mapMutations('counter', [
                    'increment',
                    'decrement',
                ]),
            }
        }

Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,⽽不是直接变更状态。
  • Action 可以包含任意异步操作

MapAction辅助函数

	import { mapMutations } from 'vuex'
        export default {
            // ...
            methods: {
                ...mapActions('counter', [
                    'incrementAsync'
                ])
            }
        }

提交方式

//在组件内部
        // 以载荷形式分发
        this.$store.dispatch('incrementAsync', {
            amount: 10
        })
        // 以对象形式分发
        this,.$store.dispatch({
            type: 'incrementAsync',
            amount: 10
        })

Module

由于使⽤单⼀状态树,应⽤的所有状态会集中到⼀个⽐较⼤的对象。当应⽤变得⾮常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有⾃⼰的 state、mutation、action、getter、甚⾄是嵌套⼦模块——从上⾄下进⾏同样⽅式的分割:

做⼀个购物⻋案例

有两个模块 cart 和 products

创建store⽂件夹

|---store
 	├── index.js
 	└── modules
 		├── cart.js
 		└── products.js

cart.js

如果希望你的模块具有更⾼的封装度和复⽤性,你可以通过添加 namespaced: true 的⽅式使其成为带命名空间的模块

当模块被注册后,它的所有 getter、action 及 mutation 都会⾃动根据模块注册的路径调整命名。

  	export default {
            //使当前模块具有更⾼的封装度和复⽤性
            namespaced: true,
            state: {
                ...
            },
            getters: {
                ...
            },
            mutations: {
                ...
            },
            actions: {
                ...
            },
        }

products.js

	export default {
            //使当前模块具有更⾼的封装度和复⽤性
            namespaced: true, state: {

            },
            getters: {

            }, mutations: {

            },
            actions: {

            },
        }

index.js

    import Vue from 'vue'
        import Vuex from 'vuex'
        Vue.use(Vuex);
        import cart from './modules/cart';
        import products from './modules/products'; export default new Vuex.Store({
            modules: {
                cart,
                products,
            }
        })
     //this.$store.state.cart //获取cart的状态
     //this.$store.state.products //获取products的状态

完整购物车案例

mock数据

新建vue.config.js

	const products = [
            {
                id: 1, title: 'iphone11', price: 600, inventory:
                    10
            },
            {
                id: 2, title: 'iphone11 pro', price: 800,
                inventory: 5
            }, {
                id: 3, title: 'iphone11 max', price: 1600,
                inventory: 6
            },
        ]
        module.exports = {
            devServer: {
                before(app, server) {
                    app.get('/api/products', (req, res) => {
                        res.json({
                            products: products
                        })
                    })
                }
            }
        }

cart.js

 	export default {
            //使当前模块具有更⾼的封装度和复⽤性
            namespaced: true,
            state: {
                items: [],
            },
            getters: {
                //获取购物⻋中的商品
                cartProducts: (state, getters, rootState) => {
                    return state.items.map(({ id, quantity })
                        => {
                        const product =
                            rootState.products.products.find(product => product.id
                                === id)
                        return {
                            title: product.title,
                            price: product.price,
                            quantity
                        }
                    })
                },
                // 购物⻋总价格
                cartTotalPrice: (state, getters) => {
                    return getters.cartProducts.reduce((total,
                        product) => {
                        return total + product.price *
                            product.quantity
                    }, 0)
                }

            },
            mutations: {
                pushProductToCart(state, { id }) {
                    state.items.push({
                        id,
                        quantity: 1
                    })
                },
                incrementItemQuantity(state, { id }) {
                    const cartItem = state.items.find(item =>
                        item.id === id);
                    cartItem.quantity++;
                },
            },
            actions: {
                //添加商品到购物⻋
                addProductToCart({ commit, state }, product) {
                    // 如果有库存
                    if (product.inventory > 0) {
                        const cartItem = state.items.find(item
                            => item.id === product.id);
                        if (!cartItem) {
                            commit('pushProductToCart', {
                                id:
                                    product.id
                            });
                        } else {
                            commit('incrementItemQuantity',
                                cartItem);
                        }
                        //提交products模块中
                        decrementProductInventory⽅法
                        //让商品列表的库存数量减1

                        commit('products/decrementProductInventory', {
                            id:
                                product.id
                        }, { root: true })
                    }

                }
            },
        }

products.js

	import Axios from "axios"; export default {
            //使当前模块具有更⾼的封装度和复⽤性
            namespaced: true,
            state: {
                products: []
            },
            getters: {

            },
            mutations: {
                setProducts(state, products) {
                    state.products = products;
                },
                //减少商品库存的⽅法
                decrementProductInventory(state, { id }) {
                    const product = state.products.find(product
                        => product.id === id)
                    product.inventory--
                }
            },
            actions: {
                //获取所有商品的⽅法
                getAllProducts({ commit }) {
                    Axios.get('/api/products')
                        .then(res => {
                            console.log(res.data.products);

                            commit('setProducts', res.data.products)
                        })
                        .catch(err => {
                            console.log(err);

                        })
                }
            },
        }

Products.vue

 <template>
        <div>
            <h3>商铺</h3>
            <ul>
                <li v-for='product in products' :key='product.id'>
                    {{product.title}} - {{product.price |
                    currency}}
                    <br> <button :disabled='!product.inventory' @click='addProductToCart(product)'>添加到购物⻋</button>
                </li>
            </ul>
            <hr>
        </div>
    </template>

    <script>
        import { mapState, mapActions } from "vuex";
        export default {
            name: "ProductList",
            data() {
                return {};
            },
            computed: {
                products() {
                    return
                    this.$store.state.products.products
                }
            },
            methods: {
                ...mapActions('cart', [
                    'addProductToCart'
                ])
            },
            created() {

                this.$store.dispatch("products/getAllProducts");
            }
        };
    </script>

Cart.vue

<template>
        <div>
            <h2>我的购物⻋</h2> <i>请增加商品到您的购物⻋.</i>
            <ul>
                <li v-for="product in products" :key="product.id">{{product.title}}-{{product.price |
                    currency}} x {{product.quantity}}
                </li>
            </ul>
            <p>总价格:{{total | currency}}</p>
        </div>
    </template>

    <script>
        import { mapGetters, mapState } from "vuex";
        export default {
            name: "shoppingcart",
            computed: {
                ...mapGetters('cart', {
                    products: 'cartProducts',
                    total: 'cartTotalPrice'
                })
            }
        };
    </script>

什么情况下我应该使⽤Vuex?

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和⻓期效益进⾏权衡。

如果您不打算开发⼤型单⻚应⽤,使⽤ Vuex 可能是繁琐冗余的。确实是如此——如果您的应⽤够简单,您最好不要使⽤ Vuex。⼀个简单的 store 模式就⾜够您所需了。但是,如果您需要构建⼀个中⼤型单⻚应⽤,您很可能会考虑如何更好地在组件外部管理状态,Vuex将会成为⾃然⽽然的选择。引⽤ Redux 的作者 Dan Abramov 的话说就是:

Flux 架构就像眼镜:您⾃会知道什么时候需要它

插件

⽇志插件

Vuex ⾃带⼀个⽇志插件⽤于⼀般的调试:

import createLogger from 'vuex/dist/logger'
const store = new Vuex.Store({
 	plugins: [createLogger({
 		collapsed: false, 
 		// ⾃动展开记录的 mutation
 	})]
})

要注意,logger 插件会⽣成状态快照,所以仅在开发环境使⽤。