2020年史上最全Vue框架整理从基础到实战(三)

file

Vue-Router

资料

介绍

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

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

起步

用 Vue.js + Vue Router 建立单页应用,是很是简单的。使用 Vue.js ,咱们已经能够经过组合组件来组成应用程序,当你要把 Vue Router 添加进来,咱们须要作的是,将组件 (components) 映射到路由 (routes),而后告诉 Vue Router 在哪里渲染它们html

安装vue

npm i vue-router -Sios

在main.js中vue-router

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)
复制代码

推荐使用:vue add router 添加插件(记得提早提交)vuex

基本使用

router.jsnpm

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')

复制代码

作好以上配置以后json

App.vueaxios

<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-linkto 属性传一个对象:

<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: '/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 {
    // ...
    props: {
        id: {
            type: String,
            default: ''
        },
        title:{
            type:String
        }
    },
};
</script>

复制代码

编程式导航

除了使用 <router-link> 建立 a 标签来定义导航连接,咱们还能够借助 router 的实例方法,经过编写代码来实现。

注意:在 Vue 实例内部,你能够经过 router 访问路由实例。所以你能够调用 this.router.push。

声明式 编程式
<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 应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

安装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 的话说就是:

Flux 架构就像眼镜:您自会知道何时须要它

插件

日志插件

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

import createLogger from 'vuex/dist/logger'

const store = new Vuex.Store({
    plugins: [createLogger({
        collapsed: false, // 自动展开记录的 mutation
    })]
})
复制代码

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

往期文章

2019年末史上最全Vue框架整理从基础到实战(一)

2019年末史上最全Vue框架整理从基础到实战(二)

2019年末史上最全Vue框架整理从基础到实战(四)

2019年末史上最全Vue框架整理从基础到实战(五)

最后

还有2件事拜托你们

一:求赞 求收藏 求分享 求留言,让更多的人看到这篇内容

二:欢迎添加个人我的微信

备注“资料”, 300多篇原创技术文章,海量的视频资料便可得到

备注“加群”,我会拉你进技术交流群,群里大牛学霸具在,哪怕您作个潜水鱼也会学到不少东西

本文由练识课堂发布!

相关文章
相关标签/搜索