购物车 Hook 封装:useShoppingCart(Vue3.2)

278 阅读1分钟

经典需求购物车,经常出现在工作中;这里边涉及商品数量变化,金额小数计算,可能的(优惠券逻辑),单独将逻辑封装出来可以让更多场景使用,提高复用性和维护性,扩展性。最终代码如下:

一、useShoppingCart.js

import { ref, reactive, computed, } from 'vue';

/**
 * 购物车逻辑
 * @param {*} items 默认商品列表
 * @param {*} options {
 *  idKey: 'id', //商品 id
 *  priceKey: 'price', //商品价格键
 *  countKey: 'count', //商品数量键(值为本地添加到购物车的特定商品数量)
 *  priceFn: (val) => parseInt(val)} //商品价格回调函数
 * } 
 * @returns {
 *  procuts, //商品数组
 *  totalPrice, //商品总价
 *  change //商品变动回调方法
 * }
 */
const useShoppingCart = (items = [], 
  options = {
    idKey: 'id', 
    priceKey: 'price', 
    countKey: 'count', 
    priceFn: (val) => parseInt(val)}
  ) => {
  const procuts = reactive(items);

  //获取单个商品的价格
  const objPrice = (obj, key) => {
    let tmpPrice = obj[key];
    if (typeof tmpPrice === 'number') {
      return tmpPrice;
    }

    let result = options.priceFn(tmpPrice);
    if (typeof result !== 'number') {
      throw new Error('价格不能是字符串');
    }
    return result;
  };

  /**
   * 商品总价
   */
  const totalPrice = computed((fractionDigits = 2) => {
    return procuts
    .map(e => objPrice(e, options.priceKey) * e[options.countKey])
    .reduce((pre, cur) => pre + cur, 0)
    .toFixed(fractionDigits);
  });
  
  /**
   * 商品添加变动
   * @param {*} num 该商品添加的数量
   * @param {*} obj 购物车中当前点击的商品对象
   */
  const change = (num, obj) => {
    console.log(num, obj);
    obj[options.countKey] = num;
  
    const idx = procuts.findIndex(e => e[options.idKey] === obj[options.idKey]);
    if (idx === -1) {
      procuts.push(obj);
    } else {
      procuts[idx][options.countKey] = num;
      if (num === 0) {
        procuts.splice(idx, 1);
      }
    }
  };
  
  return {
    procuts, 
    totalPrice, 
    change
  };
}

export{
  useShoppingCart,
}

二、使用示例 useShoppingCartDemo.vue

<template>
  <h1>{{ $route.meta.title }}</h1>
  <!-- <h1>{{ JSON.stringify(route) }}</h1> -->
  
  <hr>
  <h3>商品列表</h3> 
  <div
    class="item" 
    v-for="(item, index) in list" :key="index"
  >
    <span> {{ JSON.stringify(item) }}</span>     
    <Counter
      :count="item.count"
      @change="change"
      :obj="item"
    />
  </div>

  <hr>
  <h3>购物车</h3> 
  <div
    class="item" 
    v-for="(item, index) in procuts" :key="index"
  >
    <span> {{ JSON.stringify(item) }}</span>     
  </div>
  <div>
    <h3>总价: {{ totalPrice }}</h3>
  </div>
</template>


<script setup>
import Counter from "@/components/Counter.vue";

import { ref, reactive, computed, onMounted, } from 'vue';

import { useShoppingCart } from '@/utils/use/useShoppingCart.js';

const list = reactive([
  {
    id: '9001',
    name: '商品1', 
    price1: 100,
    count: 0,
  },
  {
    id: '9002',
    name: '商品2', 
    price1: 200,
    count: 0,
  },
  {
    id: '9003',
    name: '商品3', 
    price1: '300',
    count: 0,
  },
]);

const {procuts, totalPrice, change} = useShoppingCart([], {
  idKey: 'id', 
  priceKey: 'price1', 
  countKey: 'count', 
  priceFn: (val) => parseInt(val)
});

</script>


<style scoped lang='scss'>

.item{
  height:50px;
  border-bottom: 1px solid #eeeeee;
}
</style>

useShoppingCartDemo

三、最后:

Hook 本质是一种代码最小逻辑单元。不要在意你使用的框架,如果你感觉你的页面逻辑太复杂就大胆去封装吧!