Vuex 购物车实战

原理介绍

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

一个状态管理应用一般包含以下几个部分:

  • state,存储数据源,就是data中的数据
  • getter,获取store属性方法
  • mutations, 更改 Vuexstore 中的状态的唯一方法是提交 mutations
  • view,以声明方式将state映射到视图,就是HTML模板。
  • actions,响应在view上的用户输入导致的状态变化,就是用户触发的修改data中数据的方法。
  • modules,模块化

注意:当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
  • 来自不同视图的行为需要变更同一状态。我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

为了解决这个问题,可以把组件共享的状态抽取出来,以一个全局共享的方式管理,这就是Vuex。这样无论在哪个组件中,都能获取公共的状态或者触发行为!

项目实战

1、先看静态页面,如下图:
Vuex 购物车实战_第1张图片

当购物车商品数量进行增减操作的话,在当前页面下单个商品的总金额和底下的总件数及总价都会同时发生变化,这样我们就可以封装Vuex

views 视图文件夹中创建 cart/Home.vue 组件,把购物车静态页的html代码复制过去。

doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>长乐小卖部title>
  <meta name="description" content="">
  <meta name="keywords" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="renderer" content="webkit">
  <meta http-equiv="Cache-Control" content="no-siteapp" />
  <link rel="stylesheet" href="assets/css/common.css">
head>
<body>
<div id="wrapper">
  <div class="cart-index">
    <div class="cart-index-wrap">
      <div class="cart-list">
        <ul>
          <li class="item">
            <div class="ui-box">
              <div class="imgProduct"><a href="/1/#/product/view?product_id=1153200008"><img
                src="https://images.canon4ever.com/20160413140611_68818.jpg">a>div>
              <div class="info ui-box-flex">
                <div class="name">
                  <span>长乐未央路由器 mini 白色span>
                div>
                <div class="price">
                  <p>
                    <span>售价:span><span>799元span>
                    <span>合计:span><span>799元span>
                  p>
                div>
                <div class="num">
                  <div class="xm-input-number">
                    <div class="input-sub">div>
                    <div class="input-num"><span>1span>div>
                    <div class="input-add active">div>
                  div>
                  <div class="delete">
                    <a href="javascript:;">
                      <span class="icon-iconfontshanchu">span>
                    a>
                  div>
                div>
              div>
            div>
            <div class="append">div>
          li>
          <li class="item">
            <div class="ui-box">
              <div class="imgProduct"><a href="/1/#/product/view?product_id=1141700025"><img
                src="https://images.canon4ever.com/20160413140611_68818.jpg">a>div>
              <div class="info ui-box-flex">
                <div class="name">
                  <span>长乐未央路由器 mini 白色span>
                div>
                <div class="price">
                  <p>
                    <span>售价:span><span>129元span>
                    <span>合计:span><span>129元span>
                  p>
                  <div class="tip">
                    <span style="display: none;">请于2016/05/10 22:59前下单,逾期将失效。span>
                  div>
                div>
                <div class="num">
                  <div class="xm-input-number">
                    <div class="input-sub">div>
                    <div class="input-num"><span>1span>div>
                    <div class="input-add active">div>
                  div>
                  <div class="delete">
                    <a href="javascript:;">
                      <span class="icon-iconfontshanchu">span>
                    a>
                  div>
                div>
              div>
            div>
            <div class="append">div>
          li>
        ul>
      div>
      <div class="pointBox">
        <div class="point" style="display: none;"><span class="act act_special">包邮span><span>span>div>
        <div class="point">
          <p>温馨提示:产品是否购买成功,以最终下单为准,请尽快结算p>
        div>
      div>

      
      <div class="bottom-submit ui-box">
        <div class="price"><span>共2件 金额:span><br><strong>928strong><span>span>div>
        <div class="btn"><a class="ui-button ui-button-disable"
                            href="#"><span>继续购物span>a>div>
        <div class="btn"><a class="ui-button"
                            href="#"><span>去结算span>a>div>
      div>
    div>
  div>
div>

body>
html>

2、创建store状态管理模块

store 文件夹里面新建modules文件夹,里面继续创建carts.js 文件,写上基础格式:

// 存储数据,类似 script 里面的 data
const state = () => ({
  all: [],  // 定义 all 数组用于存储购物车所有商品数据
  total: {}  // 定义 total 对象用于存储购物车总件数、总金额
});

// getters
const getters = {};  // 用于获取数据

// 异步请求
const actions = {}; // 异步读取接口

// 更改状态
const mutations = {} // 把接口拿到的数据提交给 mutations 中定义的方法,最后把数据存储到 state 中

// 导出模块
export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
};

由于我们创建的是扩展文件 carts.js,要让这个store生效,需在 store/index.js 中引入carts.js 文件。

store/index.js 中添加如下带注释的代码代码:

import Vue from "vue";
import Vuex from "vuex";
import carts from "./modules/carts";  // 引入 carts.js 

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    carts    // 注册模块。原有的getters、actions都分开写到 carts.js 中了,所以这里的直接删掉
  }
});

3、拆分静态组件

src/components 中新建 cart 文件夹,根据上面的静态页html代码,我们在里面创建 4 个组件

List.vue 中:

<template>
  <div class="cart-list">
    <ul>
      <li class="item">
        <div class="ui-box">
          <div class="imgProduct"><a href="/1/#/product/view?product_id=1153200008"><img
            src="https://images.canon4ever.com/20160413140611_68818.jpg">a>div>
          <div class="info ui-box-flex">
            <div class="name">
              <span>长乐未央路由器 mini 白色span>
            div>
            <div class="price">
              <p>
                <span>售价:span><span>799元span>
                <span>合计:span><span>799元span>
              p>
              <div class="tip">
                <span style="display: none;">请于2016/04/11 00:58前下单,逾期将失效。span>
              div>
            div>
            <div class="num">
              <div class="xm-input-number">
                <div class="input-sub">div>
                <div class="input-num"><span>1span>div>
                <div class="input-add active">div>
              div>
              <div class="delete">
                <a href="javascript:;">
                  <span class="icon-iconfontshanchu">span>
                a>
              div>
            div>
          div>
        div>
        <div class="append">div>
      li>
      <li class="item">
        <div class="ui-box">
          <div class="imgProduct"><a href="/1/#/product/view?product_id=1141700025"><img
            src="https://images.canon4ever.com/20160413140611_68818.jpg">a>div>
          <div class="info ui-box-flex">
            <div class="name">
              <span>长乐未央路由器 mini 白色span>
            div>
            <div class="price">
              <p>
                <span>售价:span><span>129元span>
                <span>合计:span><span>129元span>
              p>
            div>
            <div class="num">
              <div class="xm-input-number">
                <div class="input-sub">div>
                <div class="input-num"><span>1span>div>
                <div class="input-add active">div>
              div>
              <div class="delete">
                <a href="javascript:;">
                  <span class="icon-iconfontshanchu">span>
                a>
              div>
            div>
          div>
        div>
        <div class="append">div>
      li>
    ul>
  div>

Total.vue 中:

<template>
  <div class="bottom-submit ui-box">
    <div class="price"><span>共2件 金额:span><br><strong>928strong><span>span>div>
    <div class="btn"><a class="ui-button ui-button-disable"
                        href="#"><span>继续购物span>a>div>
    <div class="btn"><a class="ui-button"
                        href="#"><span>去结算span>a>div>
  div>
template>

Info.vue

<div class="pointBox">
  <div class="point" style="display: none;"><span class="act act_special">包邮span><span>span>div>
  <div class="point">
    <p>温馨提示:产品是否购买成功,以最终下单为准,请尽快结算p>
  div>
div>

NoCart.vue

<template>
  <div class="cart-index">
    <div class="cart-index-wrap">
      <div class="empt">
        <div class="b3">
          <a href="#" class="ui-button ui-button-disable">
            <span>全部商品span>
          a>
          <a href="#" class="ui-button">
            <span>精选商品span>
          a>
        div>
      div>
    div>
  div>
template>

最后在views/cart/Home.vue 中引入并注册上述组件

<template>
  <div id="wrapper">
    <div class="cart-index">
      <NoCarts v-if="carts.length == 0"/>   <!-- 当购物车为空时加载空购物车组件 -->

      <div class="cart-index-wrap" v-else>   <!-- 当购物车有商品时加载购物车列表组件 -->
        <List/>
        <Info/>
        <Total/>
      </div>
    </div>
  </div>
</template>

<script>
  import NoCarts from "@/components/carts/NoCarts";
  import List from "@/components/carts/List";
  import Total from "@/components/carts/Total";
  import Info from "../../components/cart/Info";

  export default {
      components: {
      NoCarts,
      List,
      Total,
      Info
  },
  data(){
      return {
        carts:[]
      }
  },
  created() {
    this.initCart()
  },
  methods:{
      async initCart(){
          // 这里读取购物车首页的接口,拿到数据并赋值给 data
          const res = await request.get("/carts");
          this.carts = res.data
      }
  }
};
</script>

4、读取数据

store/modules/carts.js 中:

import request from "../../utils/request";  // 引入 request 请求

const actions = {
  // 读取购物车接口,获取数据
  async getAllCarts({ commit }) {
    const res = await request.get("/carts");
    // 将获取到的数据,通过commit传给mutation
    commit("setCarts", res.data);  // 这里的 setCarts 是 mutations里面接收数据的方法
  },
};

const mutations = {
  // 通过 mutations 里面的 setCarts 方法把数据载荷(赋值)给上面的 state
  setCarts(state, payload) {
    state.all = payload.carts;
    state.total = payload.total;
  }
};

由于 actions 里面的 commit 不能直接把数据赋值给 data,所以需要使用 mutations

接下来修改 views/cart/Home.vue代码如下:

.
.
.

<script>
  import NoCart from "../../components/cart/NoCart";
  import List from "../../components/cart/List";
  import Total from "../../components/cart/Total";
  import Info from "../../components/cart/Info";

  import {mapState} from "vuex";

  export default {
      name: "Home",
      components: {
          NoCart,
          List,
          Total,
          Info
      },
      computed: mapState({
          // 读取state中的数据,并赋值给carts
          carts: (state) => state.carts.all
      }),
      created() {
          // 常规写法,执行action里的方法,读取接口,并设置到state中
          this.$store.dispatch("carts/getAllCarts");
      },
  };
</script>

src/components/cart/List.vue


<template>
  <div class="cart-list">
    <ul>
      <li class="item" v-for="cart in carts" :key="cart.id">
        <div class="ui-box">
          <div class="imgProduct"><a href="/1/#/product/view?product_id=1153200008"><img
            :src="cart.product.image">a>div>
          <div class="info ui-box-flex">
            <div class="name">
              <span>{{ cart.product.name }}span>
            div>
            <div class="price">
              <p>
                <span>售价:span><span>{{ cart.product.price }}元span>
                <span>合计:span><span>{{ Number(cart.product.price * cart.num).toFixed(2) }}元span>
              p>
            div>
            <div class="num">
              <div class="xm-input-number">
                <div class="input-sub">div>
                <div class="input-num"><span>1span>div>
                <div class="input-add active">div>
              div>
              <div class="delete">
                <a href="javascript:;">
                  <span class="icon-iconfontshanchu">span>
                a>
              div>
            div>
          div>
        div>
        <div class="append">div>
      li>
    ul>
  div>
template>

<script>
  import { mapState, mapActions } from "vuex";

  export default {
    computed: mapState({
      // 读取state中的数据,并赋值给carts
      carts: (state) => state.carts.all
    }),
    methods: {
      ...mapActions("carts")  // 执行 action 中的方法获取carts数据,这里mapActions是简化写法
    }
  };
script>

最后修改 src/components/cart/Toatl.vue 中的代码:

<template>
  <div class="bottom-submit ui-box">
    <div class="price"><span>共 {{ total.num }} 件 金额:span><br><strong>{{ total.price }}strong><span>span>
    div>
    <div class="btn"><a class="ui-button ui-button-disable"
                        href="/1/#/product/category"><span>继续购物span>a>div>
    <div class="btn"><a class="ui-button"
                        href="/1/#/order/checkout?address_type=common"><span>去结算span>a>div>
  div>
template>

<script>
import { mapState } from "vuex";

export default {
  computed: mapState({
    // 读取state中的数据,并赋值给carts
    total: (state) => state.carts.total
  }),
};
script>

5、测试,刷新页面,你会发现购物车数据正常显示。

6、改变数量

把上面的逻辑步骤搞清楚后,就可以完成购物车其他操作。

store/modules/carts.js 中的 action 里面增加读取改变数量的接口方法

const actions = {
  .
  .
  .
  // 修改购物车商品数量
  async changeNum({ dispatch }, payload) {
    // 调用接口,修改购物车商品数量,payload是前端传过来的数据
    await request.put(`/carts/${payload.id}`, { type: payload.type });

    // 在action里,执行另一个action用dispatch
    await dispatch("getAllCarts");  // 重新读取数据
  },
};

修改 src/components/cart/List.vue 里面数量加减节点的代码

<template>
  <div class="cart-list">
    <ul>
      <li class="item" v-for="cart in carts" :key="cart.id">
        <div class="ui-box">
          <div class="imgProduct"><a href="/1/#/product/view?product_id=1153200008"><img
            :src="cart.product.image">a>div>
          <div class="info ui-box-flex">
            <div class="name">
              <span>{{ cart.product.name }}span>
            div>
            <div class="price">
              <p>
                <span>售价:span><span>{{ cart.product.price }}元span>
                <span>合计:span><span>{{ Number(cart.product.price * cart.num).toFixed(2) }}元span>
              p>
            div>
            <div class="num">
              <div class="xm-input-number">
                <div class="input-sub" :class="{ active:cart.num > 1}"
                     @click="changeNum({id:cart.id,type:'dec'})">div>
                <div class="input-num"><span>{{ cart.num }}span>div>
                <div class="input-add active" @click="changeNum({id:cart.id,type:'inc'})">div>
              div>
              <div class="delete">
                <a href="javascript:;">
                  <span class="icon-iconfontshanchu">span>
                a>
              div>
            div>
          div>
        div>
        <div class="append">div>
      li>
    ul>
  div>
template>

<script>
  import { mapState, mapActions } from "vuex";

  export default {
    computed: mapState({
      carts: (state) => state.carts.all
    }),
    methods: {
      ...mapActions("carts", [
        "changeNum"  // 增加修改数量方法。注意上面 html 中加减节点传参方式!!!
      ])
    }
  };
script>

7、删除购物车

store/modules/carts.js 中的 action 里面增加删除购物车的接口方法

const actions = {
  .
  .
  .
  // 删除购物车
  async deleteCart({ dispatch }, payload) {
    // 调用接口,删除购物车
    await request.delete(`/carts/${payload.id}`);

    // 在action里,执行另一个action
    await dispatch("getAllCarts");  // 删除成功后重新读取数据
  }
};

修改 src/components/cart/List.vue 里面删除购物车节点的代码

.
.
.
<div class="delete" @click="deleteCart({id:cart.id})">
    <a href="javascript:;">
      <span class="icon-iconfontshanchu">span>
    a>
div>
.
.
.

<script>
  import { mapState, mapActions } from "vuex";

  export default {
    computed: mapState({
      carts: (state) => state.carts.all
    }),
    methods: {
      ...mapActions("carts", [
        "changeNum",
        "deleteCart"  // 增加删除方法
      ])
    }
  };
script>

最后,我们来修改 views/cart/Home.vue 里面的常规写法代码如下:

.
.
.

<script>
  import NoCart from "../../components/cart/NoCart";
  import List from "../../components/cart/List";
  import Total from "../../components/cart/Total";
  import Info from "../../components/cart/Info";

  import { mapActions, mapState } from "vuex";

  export default {
      name: "Home",
      components: {
          NoCart,
          List,
          Total,
          Info
      },
      computed: mapState({
          // 读取state中的数据,并赋值给carts
          carts: (state) => state.carts.all
      }),
      created() {
          // 常规写法,执行action里的方法,读取接口,并设置到state中
          // this.$store.dispatch("carts/getAllCarts");
          // mapActions简化写法
          this.getAllCarts();
      },
      methods: {
          ...mapActions("carts", [
          "getAllCarts"
          ])
     }
  };
</script>

你可能感兴趣的:(vue.js,vue.js,前端)