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多篇原創技術文章,海量的視頻資料便可得到

備註「加羣」,我會拉你進技術交流羣,羣裏大牛學霸具在,哪怕您作個潛水魚也會學到不少東西

本文由練識課堂發佈!

相關文章
相關標籤/搜索