最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue全家桶系列之Vue-router和Vuex

    正文概述 掘金(前端开发小马哥)   2020-11-23   785

    Vue全家桶-Vue-router&Vuex

    关于Vue的路由和数据管理,面试中问的最多的我简单总结一下:

    • 路由的实现原理?
    • 项目中用的hash和history模式?除了这两种还有哪种? 用在什么环境下的?
    • 用history模式在上线的时候会出现什么问题
    • 如果你会redux,会让你比较一下vuex和redux的区别?
    • 说一下vuex的实现原理

    加油吧,老铁们,觉得不错的同学,留下你的赞和关注啊

    Vue-Router

    资料

    • Vue-router
    • Vuex

    介绍

    Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

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

    起步

    用 Vue.js + Vue Router 创建单页应用,是非常简单的。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue 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;
    

    main.js

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    
    Vue.config.productionTip = false
    
    new Vue({
      // 4.挂载根实例
      router,
      render: h => h(App)
    }).$mount('#app')
    
    

    做好以上配置之后

    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

    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: '/home',
        name: 'home',
        component: Home,
        alias: '/alias'
    }
    

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

    路由组件传参

    在组件中使用 $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 {
        // ...
        props: {
            id: {
                type: String,
                default: ''
            },
            title:{
                type:String
            }
        },
    };
    </script>
    
    

    编程式导航

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

    声明式编程式
    <router-link :to="...">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' }})
    

    前进后退

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

    嵌套路由

    实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,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 组件的模板添加一个 <router-view>

    <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 = JSON.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", JSON.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" v-model="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 => record.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>
          <p>{{ post.body }}</p>
        </div>
      </div>
    </template>
    
    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();
          });
        }
      }
    };
    

    Vuex

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

    Vue全家桶系列之Vue-router和Vuex

    安装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.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、甚至是嵌套子模块——从上至下进行同样方式的分割:

    做一个购物车案例

    有两个模块cartproducts

    创建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>
    
    <style scoped>
    </style>
    

    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>
    
    <style scoped>
    </style>
    

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

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

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

    插件

    日志插件

    Vuex 自带一个日志插件用于一般的调试:

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

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


    起源地下载网 » Vue全家桶系列之Vue-router和Vuex

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元