vue3+Ts+Hook的方式实现商城核心功能sku选择器

news2024/9/20 14:57:42

前言

Hooks是React等函数式编程框架中非常受欢迎的工具,随着VUE3 Composition API 函数式编程风格的推出,现在也受到越来越多VUE3开发者的青睐,它让开发者的代码具有更高的复用度且更加清晰、易于维护。

本文将通过CRMEB商城商品详情sku选择功能了解Hooks的使用基础以及自定义HOOK开发相关的要点,快速入门。

a5943202401031729583572.jpg

Hook简介

1.什么是hook

Hooks并不是VUE特有的概念,实际上它原本被用于指代一些特定时间点会触发的勾子。而在React16之后,它被赋予了新的意义:

一系列以 use 作为开头的方法,它们提供了让你可以完全避开 class式写法,在函数式组件中完成生命周期、状态管理、逻辑复用等几乎全部组件开发工作的能力

在VUE3中,Hooks的概念结合了VUE的响应式系统,被称为组合函数。组合函数是VUE3组合式API中提供的新的逻辑复用的方案,是一类利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数,简单来说,它就是一个创建工具的工具.

2.Hooks与composition Api

Hooks是一种基于闭包的函数式编程思维产物,所以通常我们会在函数式风格的框架或组件中使用Hook,比如VUE的组合式API(Composition Api)。Hooks在VUE2所使用的选项式风格API中也不是不可以使用,毕竟Hook本质只是一个函数,只要hook内部所使用的api能够得到支持,我们可以在任何地方使用它们,只是可能需要额外的支持以及效果没有函数式组件中那么好,因为仍会被选项分割。

VUE3推出时为开发者带来了全新的Composition API即组合式API。它是一种通过函数来描述组件逻辑的开发模式。组合式API为开发者带来了更好的逻辑复用能力,通过组合函数来实现更加简洁高效的逻辑复用。

为什么要使用Hooks

在以往VUE2的选项式API中,主要通过Mixin或是Class继承来实现逻辑复用,但这种方式有三个明显的短板:

1.不清晰的数据来源:当使用了多个mixin/class时,哪个数据是哪个模块提供的将变得难以追寻,这将提高维护难度

2.命名空间冲突:来自多个class/mixin的开发者可能会注册同样的属性名,造成冲突

3.隐性的跨模块交流:不同的mixin/class之间可能存在某种相互作用,产生未知的后果

以上三种主要的缺点导致在大型项目的开发中,多mixin/class的组合将导致逻辑的混乱以及维护难度的提升,因而在VUE3的官方文档中不再继续推荐使用,保留mixin也只是为了迁移的需求或方便VUE2用户熟悉。

mixin的缺点其实就是Hooks的优点:

1.清晰一目了然的源头

2.没有命名冲突的问题

3.精简逻辑

怎么开始玩Hooks

Hooks的各类规范

1.通常来讲,一个Hook的命名需要以use开头,比如useTimeOut,这是约定俗成的,开发者看到useXXX即可明白这是一个Hook。Hook的名称需要清楚地表明其功能。

2.只在当前关注的最顶级作用域使用Hook,而不要在嵌套函数、循环中调用Hook

3.函数必须是纯函数,没有副作用

4.返回值是一个函数或数据,供外部使用

5.Hook内部可以使用其他的Hook,组合功能

6.数据必须依赖于输入,不依赖于外部状态,保持数据流的明确性

7.在Hook内部处理错误,不要把错误抛出到外部,否则会增加hook的使用成本

8.Hook是单一功能的,不要给一个Hook设计过多功能。单个Hook只负责做一件事,复杂的功能可以使用多个Hook互相组合实现,如果给单个Hook增加过多功能,又会陷入过于臃肿、使用成本高、难维护的问题中

下面通过一个简单的hooks感受一下它的魅力:

这是一个控制页面弹窗或者抽屉显示或隐藏的hook,在以往vue2中,我们实现这样一个功能,需要在data中定义一个变量,在methods中大概率会写两个方法分别控制弹窗的显示和隐藏,如果页面有多个这样的显隐组件,我们的代码简直是灾难,糟糕的事,我们的代码中这样的案例实在是太多了,有了hooks就完全不一样了.

这是一个useBoolean的hooks,可以看到它抛出了一个响应式的布尔值和四个方法.在使用的组件内就可以多次使用该方法,从而简化代码

import { ref } from 'vue';

/**
 * boolean组合式函数
 * @param initValue 初始值
 */
export default function useBoolean(initValue = false) {
  const bool = ref(initValue);

  function setBool(value: boolean) {
    bool.value = value;
  }
  function setTrue() {
    setBool(true);
  }
  function setFalse() {
    setBool(false);
  }
  function toggle() {
    setBool(!bool.value);
  }

  return {
    bool,
    setBool,
    setTrue,
    setFalse,
    toggle,
  };
}

3e1e0202401031733568131.png

通过这个例子发现,我们在vue2中大概率要写6个方法和定义三个变量的工作在vue3配合Hooks的情况下,三行代码就实现了.

下面进入我们本文的重点,通过hooks的方式实现sku选择器的功能.

a183f202401031730208119.png

在CRMEB各个项目中,加购功能并不是只有在商品详情页使用,还有很多页面也有使用,比如商品分类的几个模板,购物车页面,搭配购等,都会需要到打开sku选择商品规格的功能,改功能包含选择商品规格,价格,库存,规格图跟随切换实时变化,还有加购数量的操作,对库存为0的规格做不可操作的限制等等,所以这段代码在前端是非常臃肿庞大的一部分代码,牵扯的业务复杂,功能广泛,若是在需要的组件内每次复制粘贴,代码量就会非常庞大,所以若是可以将这部分功能单独抽离出来整理为一个可调用的方法就非常适合我们的使用场景.

先截图看看以前vue2的方式书写的该段代码.

bbf1a202401031712117992.png

f4c36202401031712371200.png

下面是我用vue3+ts+hooks的方式实现一下,代码如下:

import { ref, reactive, watch, unref } from 'vue';
import { cloneDeep } from 'lodash-es';
export default function useSkuSelect(productInfo: Product.Details) {
  watch(productInfo, () => {
    attr.productAttr = cloneDeep(productInfo.productAttr);
    DefaultSelect();
  });
  // 向sku选择器传递的数据
  const attr = reactive({
    productAttr: [],
    productSelect: createDefaultModel(),
  });
  const attrTxt = ref('请选择');
  const attrValue = ref('');
  attr.productAttr = productInfo.productAttr;
  function DefaultSelect() {
    let productAttr = attr.productAttr;
    let valueObj: Array = [];
    let value: Array = [];
    let productValue = productInfo.productValue;
    for (const key in productValue) {
      if (Object.prototype.hasOwnProperty.call(productValue, key)) {
        const element = productValue[key];
        if (element.stock > 0) {
          valueObj = attr.productAttr.length ? key.split(',') : [];
          break;
        }
      }
    }
    // 处理已售罄时默认选中第一个
    if (!valueObj.length && productAttr.length) {
      // value = Object.keys(productValue)[0].split(',');
    } else {
      value = valueObj;
    }
    for (let index = 0; index < productAttr.length; index++) {
      productAttr[index]!.index = value[index];
    }
    // 排序
    type selectPro = Pick;
    let productSelect: selectPro = productValue[value.join(',')];
    if (productSelect && productAttr.length) {
      attr.productSelect = createProductSelect(1, productSelect);
      attrValue.value = value.join(',');
      attrTxt.value = '已选择';
    } else if (!productSelect && productAttr.length) {
      attr.productSelect = createProductSelect(2, productSelect);
      attrValue.value = '';
      attrTxt.value = '请选择';
    } else if (!productSelect && !productAttr.length) {
      attr.productSelect = createProductSelect(3, productSelect);
      attrValue.value = '';
      attrTxt.value = '请选择';
    }
  }

  function attrVal(val: Product.AttrVal) {
    const { index, indexn } = val;
    const attrValue = attr.productAttr[index]!.attr_values[indexn];
    attr.productAttr[index]!.index = attrValue;
  }
  function ChangeAttr(res: any) {
    let productSelect = productInfo.productValue[res];
    if (productSelect && productSelect.stock >= 0) {
      attr.productSelect = createProductSelect(1, productSelect);
      attrValue.value = res;
      attrTxt.value = '已选择';
    } else {
      attr.productSelect = createProductSelect(2, productSelect);
      attrValue.value = '';
      attrTxt.value = '请选择';
    }
  }
  /**
   *
   * @param type
   * true 加
   * false 减
   */
  function changeCartNum(type: boolean) {
    // 获取当前变动属性
    let proSelect = productInfo.productValue[unref(attrValue)];
    //无属性值即库存为0;不存在加减;
    if (!proSelect) return;
    let stock = proSelect.stock || 0;
    if (attr.productSelect.cart_num) {
      if (type) {
        attr.productSelect.cart_num++;
        if (attr.productSelect.cart_num > stock) {
          attr.productSelect.cart_num = stock ? stock : 1;
        }
      } else {
        if (attr.productSelect.cart_num <= 1) {
          attr.productSelect.cart_num = 1;
        } else {
          attr.productSelect.cart_num--;
        }
      }
    }
  }

  function createProductSelect(type: number, productSelect: any): Product.selectPro {
    let proSelect: Product.selectPro = createDefaultModel();
    if (type === 1) {
      proSelect = {
        store_name: productInfo.storeInfo.store_name,
        image: productSelect.image,
        price: productSelect.price,
        stock: productSelect.stock,
        unique: productSelect.unique,
        cart_num: 1,
        vip_price: productSelect.vip_price,
      };
    } else if (type === 2) {
      proSelect = {
        store_name: productInfo.storeInfo.store_name,
        image: productInfo.storeInfo.image,
        price: productInfo.storeInfo.price,
        stock: 0,
        unique: '',
        cart_num: 0,
        vip_price: productInfo.storeInfo.vip_price,
      };
    } else if (type === 3) {
      proSelect = {
        store_name: productInfo.storeInfo.store_name,
        image: productInfo.storeInfo.image,
        price: productInfo.storeInfo.price,
        stock: productInfo.storeInfo.stock,
        unique: '',
        cart_num: 1,
        vip_price: productInfo.storeInfo.vip_price,
      };
    }
    return proSelect;
  }

  function createDefaultModel(): Product.selectPro {
    return {
      store_name: '',
      image: '',
      price: '',
      stock: 0,
      vip_price: '',
      unique: '',
      cart_num: 0,
    };
  }

  return {
    ChangeAttr,
    attrVal,
    changeCartNum,
    attrValue,
    attrTxt,
    attr,
  };
}

在使用sku选择器组件的页面上使用:

08ddd202401031735273907.png

1228d20240103173613725.png

这是一个管理sku选择器内商品规格选择的Hook,在使用时只需传入该商品的详情数据以及一些配置项即可快默认选中,节省了大量重复的控制代码,使用该Hook后只需调用useSkuSelect即可实现规格的切换,加购数量的控制等等,且继承原接口的类型.因为本人其实也是hooks小白,处于学习阶段,书写的该hook和ts代码有可能并不规范,欢迎读者交流指正.

总结

Hooks是VUE3中利用组合式API响应式的特性的,实现简单高效的逻辑复用、提高开发效率、提高VUE模块可维护性的工具。Hooks的组合可以让组件低代价、高效率地实现高复杂度业务,Hooks之间通常相互独立,没有过度耦合,降低后期陷入维护地狱的风险,而且可以使得功能模块更加易于测试.使用开源的Hook将为开发带来很多方便,而开发自定义Hook则需要花费一些时间,但在实现后,高度的定制化将为项目开发带来巨大的便利.Hooks的出现不意味着抛弃Class,Hooks也有自己的缺点比如内存泄漏和可能的性能问题。Class更加易于上手,在经验丰富、技术深厚的开发者手中也可以一定程度上避开Class的缺点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1354590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Bee的批量插入与事务使用

* Bee 在2.2之前,调用批量插入在每个批都会提交commit,但在2.2改为只调用一次且在事务中,在批量插入的方法内容不再提交,而由事务控制. * * 2.2之前,批量插入使用每一个批次提交一次事务; * 这样,当违反主键约束等就忽略的大批量插入效率是很高的; * 但当事务中有批量插…

Golang Leetcode19 删除链表的倒数第N个节点 递归 双指针法+迭代

删除链表的倒数第N个节点 leetcode19 递归 由于本体是倒数第几个节点&#xff0c;非常适合递归 从终到始 的运行方式 func removeNthFromEnd(head *ListNode, n int) *ListNode {// 创建一个虚拟头节点&#xff0c;简化边界条件处理dummy : &ListNode{Next: head}//检查…

【卫星科普】什么是农业一号卫星和农业二号卫星?

农业一号卫星和农业二号卫星是中国自主研发的两颗重要卫星&#xff0c;主要用于农业领域的监测和研究。 农业一号卫星是中国第一颗具备红边波段传感器的卫星&#xff0c;也是世界上第一颗具备红边波段的宽视场多光谱中高分辨率卫星。这对农业农村遥感监测非常重要&#xff0c;…

阿里云ECS服务器无法访问端口(防火墙在关闭状态也启作用)

问题&#xff1a;一直用得好好的端口&#xff0c;突然在某一时间不可以访问这个端口了 &#xff0c;在服务器录入外网地址访问如下图&#xff1a; 先按正常流程检测&#xff1a; 1 先云服务商的管理网站查看防火墙端口是否开放 看了正常开放了端口&#xff0c;如下图&#xff…

HTML5-简单文件操作

文件操作 简介 概念&#xff1a;可以通过file类型的input控件或者拖放的方式选择文件进行操作 语法格式&#xff1a; <input type"file" multiple>属性 multiple&#xff1a;表示是否选择多个文件 accept&#xff1a;用于设置文件的过滤类型&#xff08;MI…

Linux 如何 kill 指定的 python 进程

文章目录 写在前面一、显示python相关的进程二、找到自己想要 kill 的进程&#xff0c;执行下述指令 写在前面 自己的系统是 Ubuntu 20.04 一、显示python相关的进程 ps -ef | grep python显示结果如下 其中&#xff0c;第二列分别是各个进程的 PID 号。 二、找到自己想要…

【38 Pandas+Pyecharts | 奥迪汽车销量数据分析可视化】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 数据处理 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 奥迪用户购车时间分布3.2 奥迪各系销量占比饼图3.3 奥迪各系销量…

使用Python和Pygame库创建简单的的彩球效果

简介 Pygame是一款强大的游戏开发库&#xff0c;可以用于创建各种有趣的图形效果。为了更好地了解Pygame的功能&#xff0c;今天我们将要做的是在屏幕上随机生成一些彩色的小球&#xff0c;并使它们以不同的速度和方向移动。当小球碰到屏幕边缘时&#xff0c;它们将反弹。 功能…

小型洗衣机哪个牌子质量好?五款最好用的迷你洗衣机品牌

不得不说洗衣机的发明解放了我们的双手&#xff0c;而我们从小到大就有这个意识&#xff0c;贴身衣物不可以和普通的衣服一起丢进去洗衣机一起&#xff0c;而内衣裤上不仅有肉眼看见的污渍还有手上根本无法消灭的细菌&#xff0c;但是有一款专门可以将衣物上的细菌杀除的内衣小…

仓库出入库登记系统的推荐

在信息时代&#xff0c;仓库管理已成为企业不可缺少的一项工作。我们如何高效、准确地管理仓库的进货、出货以及库存&#xff0c;是每个企业或仓管都需要面对的问题。而一个优秀的仓库出入库登记系统&#xff0c;则能够大大提升仓库管理的效率和准确性。本文将为您推荐一款实用…

NFC物联网开发在智慧校园中的应用

近年来&#xff0c;校园信息化建设速度加快&#xff0c;以物联网为基础、以各种应用服务系统为载体的智慧校园将教学、管理和校园生活充分融合&#xff0c;形成了工作、学习和生活的一体化环境。沉寂已久的NEC 技术&#xff0c;得益于智能手机的普及、无线网络数据速率提高&…

『开发工具篇』- 配置 gradle 等相关依赖镜像源

『开发工具篇』- 配置 gradle 等相关依赖镜像源 1.更换gradle下载源2. 配置setting.gradlekts文件gradle文件 1.更换gradle下载源 使用腾讯云的镜像库https://mirrors.cloud.tencent.com/gradle/ gradle-x.x-all.zip&#xff1a;编译后的二进制发布版以及源码和文档gradle-x.…

FreeRTOS——优先级翻转

1.优先级翻转概念 优先级翻转&#xff1a;高优先级的任务反而慢执行&#xff0c;低优先级的任务反而优先执行 注意&#xff1a;在实时操作系统中不允许出现&#xff0c;在二值信号量中经常出现 2.优先级翻转实战 2.1freertos_demo.c #include "freertos_demo.h" #i…

1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

1.69寸SPI接口240280TFT液晶显示模块使用中碰到的问题说明并记录一下&#xff0c; 在网上买了1.69寸液晶显示模块&#xff0c;使用spi接口&#xff0c;分辨率240280&#xff0c;给的参考程序是GPIO模拟的SPI接口&#xff0c;打算先移植到FreeRtos测试&#xff0c;再慢慢使用硬件…

Algorithm-Left Edge算法

算法输入&#xff1a; 多个段&#xff0c;每个段由两个值表示&#xff0c;例如&#xff08;1&#xff0c;3&#xff09; 算法原理&#xff1a; 将多个段按照左边的值排序放到列表中遍历列表&#xff0c;不断选择没有重叠的段&#xff0c;直到列表遍历结束&#xff0c;将选择…

rk3588中编译带有ffmpeg的opencv

有朋友有工程需要&#xff0c;将视频写成mp4&#xff0c;当然最简单的方法当然是使用opencv的命令 cv::VideoWriter writer;bool bRet writer.open("./out.mp4", cv::VideoWriter::fourcc(m, p, 4, v), 15, cv::Size(640, 512), 1); 但是奈何很难编译成功&#xff…

【python入门】day12:bug及其处理思路

bug的常见类型 粗心 / 没有好习惯 思路不清 lst[{rating:[9.7,2062397],id:1292052,type:[犯罪,剧情],title:肖申克的救赎,actors:[蒂姆罗宾斯,摩根弗里曼]},{rating:[9.6,1528760],id:1291546,type:[剧情,爱情,同性],title:霸王别姬,actors:[张国荣 ,张丰毅 , 巩俐 ,葛优]},{r…

redis的使用、打开、关闭的详细介绍

redis的使用、打开、关闭的详细介绍 1.安装redis cd / cd opt/ wget https://download.redis.io/releases/redis-5.0.5.tar.gz 2.解压redis tar xzf redis-5.0.5.tar.gz 3.执行make cd redis-5.0.5/ make 如果出现找不到make的情况就yum install -y make 如果没有gcc就…

网络安全—SSL安全访问应用

文章目录 网络拓扑部署CA服务器颁发证书开启Web服务安装IIS服务修改Web默认网页 申请Web证书前提准备申请文件生成申请web证书开始安装web证书 客户机访问web默认网站使用HTTP使用HTTPS 为客户机安装浏览器证书 环境&#xff1a;Windows Server 2003 网络拓扑 这里使用NAT还是…

力扣hot100 二叉树的直径

&#x1f468;‍&#x1f3eb; 题目地址 一个节点的最大直径 它左树的深度 它右树的深度 &#x1f60b; AC code /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* Tr…