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

前言

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的缺点

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【完整思路】2023 年中国高校大数据挑战赛 赛题 B DNA 存储中的序列聚类与比对

2023 年中国高校大数据挑战赛 赛题 B DNA 存储中的序列聚类与比对 任务 1.错误率和拷贝数分析&#xff1a;分析“train_reads.txt”和“train_reference.txt”数据集中的错误率&#xff08;插入、删除、替换、链断裂&#xff09;和序列拷贝数。 2.聚类模型开发&#xff1a;开发…

Vue3+TS+ElementPlus的安装和使用教程【详细讲解】

前言 本文简单的介绍一下vue3框架的搭建和有关vue3技术栈的使用。通过本文学习我们可以自己独立搭建一个简单项目和vue3的实战。 随着前端的日月更新&#xff0c;技术的不断迭代提高&#xff0c;如今新vue项目首选用vue3 typescript vite pinia……模式。以前我们通常使用…

【教学类-43-15】 20240103 (5宫格数独:内存数据不够计算) 不重复的基础模板数量:未知

背景需求&#xff1a; 测试5宫格有多少种不重复的基础模板&#xff08;只测试所有的25数字一组有多少个&#xff09; # 测试11*11格&#xff0c;2*2一共4套3*3 宫格目的&#xff1a;数独14 5宫格有不同的基础模板 作者&#xff1a;阿夏 时间&#xff1a;2024年01月04日 13:…

【Echarts实践案例】如何在线图上标记一个非轴线上的点

需求背景&#xff1a; 当前有一个趋势图&#xff0c;横坐标表示灯泡平均使用时长&#xff0c;纵坐标表示灯泡平均使用温度。现在需要在当前坐标系下标记一个正在使用中的灯泡的时长及温度&#xff08;趋势图表示的是计算出的平均温度&#xff0c;所以当前灯泡的温度可能不会在…

算法导论复习——CHP22 基本图算法

图的表示 邻接矩阵和邻接表 稀疏图一般用邻接表表示&#xff08;稀疏图&#xff1a;边数|E|远小于的图 &#xff09; 稠密图更倾向于用邻接矩阵表示 (稠密图&#xff1a;边数|E|接近的图) 邻接矩阵可用于需要快速判断任意两个结点之间是否有边相连的应用场景。 如果用邻…

纯前端上传word,xlsx,ppt,在前端预览并下载成图片(预览效果可以,下载图片效果不太理想)

纯前端上传word,xlsx,ppt,在前端预览并下载成图片&#xff08;预览效果可以&#xff0c;下载图片效果不太理想&#xff09; 一.安装依赖二、主要代码 预览效果链接: https://github.com/501351981/vue-office 插件文档链接: https://501351981.github.io/vue-office/examples/d…

使用(?<!pattern) 负向后行断言正则表达式提取一个双引号开头和结尾的字符串

如下是一段java代码&#xff0c;我想用正则表达从中提取代码中的字符串 cond_buffer.append(" ORDER BY \"name\" \"").append(join(order_by_column,"\","));java是通过前后用双引号包含定义字符串的。但简单使用正则表达式".…

Kubernetes Gateway API V1.0:您应该切换吗?

自Kubernetes Gateway API 发布 v1.0以来已经过去两个多月了&#xff0c;这标志着其一些关键 API 已经进入普遍可用状态。 去年&#xff0c;当网关 API升级为测试版时&#xff0c;我曾写过有关该 API的文章&#xff0c;但一年后&#xff0c;问题仍然存在。您是否应该从 Ingres…

Python----matplotlib库

目录 plt库的字体&#xff1a; plt的操作绘图函数&#xff1a; plt.figure(figsizeNone, facecolorNone): plt.subplot(nrows, ncols, plot_number)&#xff1a; plt.axes(rect)&#xff1a; plt.subplots_adjust(): plt的读取和显示相关函数&#xff1a; plt库的基础图…

Python内置类属性__module__属性的使用教程

概要 在Python中&#xff0c;每个对象都有一些内置的属性&#xff0c;这些属性提供了有关对象的一些信息。其中一个内置属性是__module__属性。__module__属性是一个字符串&#xff0c;它表示定义了类或函数的模块的名称。在本篇文章中&#xff0c;我们将详细介绍__module__属…

随机森林,Random Forests Classifiers/Regressor

目录 介绍&#xff1a; 一、 Random Forests Classifiers&#xff08;离散型&#xff09; 1.1 数据处理 1.2建模 1.3特征值权值分析 1.4 特征值的缩减 二、Random Forests Regressor&#xff08;连续型&#xff09; 2.1数据处理 2.2建模 2.3调参 介绍&#xff1a; …

数据库:基础SQL知识+SQL实验1

&#xff08;1&#xff09;基础知识&#xff1a; 1.创建数据库&#xff1a; CREATE DATABASE <database_name> 2.删除数据库&#xff1a; DROP DATABASE <database_name> 3.相关数据类型&#xff1a; [1] 字符串类型 CHAR(n)&#xff1a;固定长度的字符数据…

基于ssm的《数据库系统原理》课程平台的设计与实现论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

[蓝桥杯学习]​树上差分

差分 前缀和 sum_i sum_i-1 a_i 差分 diff_i a_i - a_i-1 差分的好处 点的差分 问题引入 解决问题 要用到差分的思想&#xff0c;每次从叶子向上的回溯&#xff0c;会让父结点子结点的cnt值&#xff0c;但是仅仅这样&#xff0c;还不行 回溯的过程中&#xff0c;LCA被…

【Midjourney】AI绘画新手教程(一)登录和创建服务器,生成第一幅画作

一、登录Discord 1、访问Discord官网 使用柯學尚网&#xff08;亲测非必须&#xff0c;可加快响应速度&#xff09;访问Discord官方网址&#xff1a;https://discord.com 选择“在您的浏览器中打开Discord” 然后&#xff0c;注册帐号、购买套餐等&#xff0c;在此不做缀述。…

[每周一更]-(第56期):不能不懂的网络知识

作为程序员&#xff0c;在网络方面具备一定的知识和技能是非常重要的。以下是一些程序员需要熟练掌握的网络知识&#xff1a; 基础网络概念&#xff1a; IP地址&#xff1a;了解IPv4和IPv6地址的格式和分配方式&#xff0c;以及常见的IP地址分类。子网掩码&#xff1a;理解子…

大数据 - Doris系列《一》- Doris简介

目录 &#x1f436;1.1 Doris 概述 &#x1f436;1.2 OLAP和OLTP&#xff08;面试&#xff09; 1. 应用场景 &#x1f959;联机事务处理OLTP(On-Line Transaction Processing) &#x1f959;联机分析处理OLAP(On-Line Analytical Processing) 2. OLAP和OLTP比较--“用户行…

大数据技术在民生资金专项审计中的应用

一、应用背景 目前&#xff0c;针对审计行业&#xff0c;关于大数据技术的相关研究与应用一般包括大数据智能采集数据技术、大数据智能分析技术、大数据可视化分析技术以及大数据多数据源综合分析技术。其中&#xff0c;大数据智能采集数据技术是通过网络爬虫或者WebService接…

Linux程序、进程以及计划任务(第一部分)

目录 一、程序和进程 1、什么是程序&#xff1f; 2、什么是进程&#xff1f; 3、线程是什么&#xff1f; 4、如何查看是多线程还是单线程 5、进程结束的两种情况&#xff1a; 6、进程的状态 二、查看进程信息的相关命令 1、ps&#xff1a;查看静态的进程统计信息 2、…

WEB:探索开源PDF.js技术应用

1、简述 PDF.js 是一个由 Mozilla 开发的开源 JavaScript 库&#xff0c;用于在浏览器中渲染 PDF 文档。它的目标是提供一个纯粹的前端解决方案&#xff0c;摆脱了依赖插件或外部程序的束缚&#xff0c;使得在任何支持 JavaScript 的浏览器中都可以轻松地显示 PDF 文档。 2、…