从零撸一个pc端vue的ui组件库( 计数器组件 )


Posted in Javascript onAugust 08, 2019

听到计数器这个名字很多人是不是一瞬间没有什么印象, 毕竟这个组件用的比较少,就是那种左边一个'-'右边一个'+', 控制某些数量的时候才会用到, 比如我之前做的商城小程序只有'下单'页面的规格弹出框里面才有他的身影, 如果是涉及到处理商品数量很频繁的业务场景应该会很常见吧, 但是不要看这个组件小, 编写它的时候坑还不少, 本次我们就来做一个计数器, 目标就是尽可能小, 尽可能的省性能.

1:需求分析

  • 每次+1 -1是常态, 但是如果搞活动, 每次最少为+-2个或三个, 就要兼容一下了,( 举一个实际遇到的坑, 我们之前把用户限制为每次活动, 每个用户只能买2个, 但是没有做好防备, 导致用户可能这次只买一个, 而下次他再次购买的时候会提示每次只能买两个, 但显示他只点击了买一个, 因为他已经买过一个, 为了兼容这个问题, 搞得还要加莫名其妙的补救代码 )
  • 中间的显示区应该可输入的, 用户想买1000个不可能让他+1+1+1..., 某些组件采用的是, 平时其为div, 点击之后变为input, 个人感觉完全没必要, 一个元素就够了, 何必搞两个元素, input状态下把他的默认样式去掉就好了.
  • 左右两边要有限制, 很多时候会有限购一说, 比如我做的商城, 库存只有10个 或者单个用户最多购买3个, 最少买两个等等限制.
  • 小数位数的显示一说... 这个其实我还真遇到过, 有一种需求叫做, 只要涉及数字就必须精确到后两位, 这种需求会导致后台同学对数据库做一定的限制, 从而我们传给后台的数据也就存在限制了.

2: 基本结构:

先展示一章普通状态的图, 让我们更直观的去完成它, 造型比较别致, 是本套组件的一个特点, 哈哈做的与别人一样会导致思想的禁锢, 自己写代码多尝试新的东西, 但是工作中一定要中规中矩, 以公司条款为准则.

从零撸一个pc端vue的ui组件库( 计数器组件 )

vue-cc-ui/src/components/InputNumber/index.js

import inputNumber from './main/input-number.vue'
inputNumber.install = function(Vue) {
 Vue.component(inputNumber.name, inputNumber);
};
export default inputNumber

vue-cc-ui/src/components/InputNumber/main/input-number.vue

<template>
 <div class="cc-input-number">
 // 左侧的':heavy_minus_sign:'符号
 <div class="cc-input-number__reduce">
  // 自己封装的icon组件, 出镜率还挺高:smirk_cat:.
  <cc-icon name='cc-reduce2'/>
 </div>
 // 中间的显示与输入部分,让人又爱又恨的number属性
 // 下面的属性就能干掉凡人的上下按钮
 // input::-webkit-outer-spin-button,
 // input::-webkit-inner-spin-button {
 // -webkit-appearance: none;
 // }
 <input ref="input"
   type="number"
   class="cc-input-number__input">
 <div class="cc-input-number__add">
  <cc-icon name='cc-add2'/>
 </div>
 </div>
</template>

这里我们选择吧input与button放在一个div里面, 且同级别这种方式, 与其他的不太一样, 因为这样更直观, 而且也足够实现我想要的功能.

3: 事件的绑定

// 减少
 <div class="cc-input-number__reduce"
   @click='reduce'>
 // 增加
<div class="cc-input-number__add"
   @click="add">
// 输入框的监控
<input ref="input"
  type="number"
  class="cc-input-number__input"
  @input="inputChange($event)">

这里我们有个问题, 就是本组件采用的是v-model的形式编写, v-model有一些弊端, 在测试的时候我发现, 比如说用户为多个组件绑定了相同的v-model会导致无限渲染的bug, 下面会解读解决这类bug的相关代码.

prpos

props: {
 max: { type: Number }, // 数字不传默认是undefined
 min: { type: Number },
 step: { // 每次计算的单位
  type: Number,
  default: 1
 },
 value: { 
 // 绑定的数值, 这里允许两种type, 为了方便用户书写,具体判断下面我们自己写
  type: [String, Number],
  required: true
 },
 precision: { // 显示小数点后几位数
  type: Number,
  validator(value) {
  if (value < 1 || value === undefined) {
   return 1;
  } else {
   return parseInt(value);
  }
  }
 }
 },

add 方法的实现

add() {
// 很可能用户就输入了一个string属性, 
// 1: 比如后台返回的就是字符串;
// 2: input框输入的就是字符串类型;
// 3: 用v-model绑定了同样的值的其他组件赋予了这个值string类型;
 let num = Number(this.value) + this.step; // 加上固定的长度
// 这里我们抽象出一个专门负责数值的变化的函数
 this.emitVal(num);
},

reduce 方法的实现

reduce() {
  let num = Number(this.value) - this.step;
  this.emitVal(num);
 },

监听input框的输入事件

inputChange(e) {
// 这里就有可能出现string类型的了
  this.emitVal(Number(e.target.value));
 },

关键性的赋值函数

emitVal

emitVal(newVal) {
  let { max, min } = this;
  // 不传参数的时候默认值就是undefined
  // 对这个值的限制就是, max之内, min以上
  if (max !== undefined && newVal > max) newVal = max;
  if (min !== undefined && newVal < min) newVal = min;
  // 这里兼容一下位数控制
  let value = Number(newVal).toFixed(this.precision);
  // 这个oldVal下面会解释:point_down:
  if (value === this.oldVal) return;
  this.oldVal = ls;
  // 发出两个事件, 一个负责改变value, 一个负责返回给用户
  // 毕竟用户不可能监听input事件然后再把值附上去, 太麻烦
  this.$emit("input", value);
  this.$emit("change", value);
  // 这一步很重要
  // 下面会详细说
  this.$refs.input.value = value;
 }

上面遗留的问题,这里解释一下.

oldVal: 能防止很多多余的改变, 比如说用户复制粘贴了一组数进来, 这个数大于max, 但是当时显示的数值就是max, 所以就不用渲染了, 或者v-model不止绑定了这个组件, 还绑定了其他各种组件, 导致值超出范围, 这边也会进行相应的限制, 而这个oldVal 就是上一个合法的值, 所以在做完检测之后, 检测通过的数值要赋值给他.

this.$refs.input.value = value; 这一步看似很没用, 因为输入框里面的是value, value改变input里面的值自然会改变, 但是实际测试并不是这样, 问题也是出现在v-model上, 绑定很多的时候会出现值的不改变, 可能是vue的机制问题, 而且他要放在 this.$emit(....);下面操作, 如果放在上面会导致多次执行, 因为他的执行会循环触发input的监听事件, 多次试验之后, 还是放在这里没有bug.
上面的两个问题都是涉及到v-model的问题, 下面还有一个同类的问题, 我们来看看.

对value进行的监控

因为value的变化, 不一定全是 通过+-输入这三种方式, 还有第三方通过v-model的方式, 还有用户手动乱填的方式.

watch: {
 value: {
  handler() {
  // 为了解决, 多组件共同v-model采用的这个方法, 也算是另辟蹊径了
  let { value, time } = this;
  clearTimeout(time);
  // 毕竟把它放入宏任务Macrotasks可以躲过很多无限循环.
  time = setTimeout(() => {
   if (value !== undefined) this.emitVal(value);
  });
  },
  // 这个是开启进页面的瞬间就出发一次的意思, 很有用, 但是数据稍大会消耗性能, 慎用
  // watch还有一个deep属性, 更是吃性能吃的厉害, 可以深度监控里面的数据
  immediate: true
 }
 }

上面的问题都是基于v-model的, 所以很早就有人剔除双向绑定的坏处, 封装越多的组件感觉就越明显.

4: 关于样式的判定

在计算属性里面我们队当前值进行了监控, 返回的是置灰的颜色, 这个让用户自定的意义不大, 所以直接写了.

computed: {
 valueMin() {
  if (this.value === this.min) return "#bbbbbb";
  return "";
 },
 valueMax() {
  if (this.value === this.max) return "#bbbbbb";
  return "";
 }
 },

dom, 点击到了最大值的话就会置灰, 我们上面已经阻止了继续点击的渲染

<cc-icon size='25px'
   name='cc-add2'
   :color="valueMax" />

做点有意思的事

slot是个自由度很高的标签

把左右按钮都包上, 让用户可以自己定义显示的标签是什么样子的

<div class="cc-input-number__reduce"
   @click='reduce'>
  <slot name='left'>
  <cc-icon size='25px'
     name='cc-reduce2'
     :color='valueMin' />
  </slot>
 </div>
vue-cc-ui/src/style/inputNumber.scss

@import './common/var.scss';
@import './common/extend.scss';
@import './common/mixin.scss';
@import './config/index.scss';

@include b(input-number) {
// 友好的小手
 cursor: pointer;
 // 有个放大动画, 看过我文章的同学都知道, 操作类的组件, 我喜欢有一个悬停放大效果.
 transition:all .1s;
 align-items: center;
 display: inline-flex;
 background-color: white;
 &:hover {
 // 放大被其他组件挡住就划不来了
  z-index: 6;
  transform: scale(1.2);
 }
 // 招牌阴影
 @include commonShadow($--color-black);
 @include e(add) {
  @include flexCenter();
  padding: 4px 6px;
 }
 @include e(reduce) {
  @include flexCenter();
  padding: 4px 6px;
 }
 @include e(input) {
 // 去掉输入框的默认样式
  border: none;
  outline:none;
  display: block;
  text-align: center;
  width:60px;
  height: 20px;
 }
}

效果展示

从零撸一个pc端vue的ui组件库( 计数器组件 )

从零撸一个pc端vue的ui组件库( 计数器组件 )

end

总的来说是这些组件中比较简单的一个了, 有些坑能够让我更好的学习vue以及前端的思想, 总的来说挺有趣的.

大家继续一起学习,一起进步, 早日实现自我价值!!

下一集准备聊聊 tab切换组件的相关知识;

github: 链接描述

Javascript 相关文章推荐
JavaScript代码复用模式实例分析
Dec 02 Javascript
js和as的稳定传值问题解决
Jul 14 Javascript
js实现网页随机切换背景图片的方法
Nov 01 Javascript
jQuery中toggle()函数的使用实例
Apr 17 Javascript
AngularJS 过滤与排序详解及实例代码
Sep 14 Javascript
xmlplus组件设计系列之下拉刷新(PullRefresh)(6)
May 03 Javascript
vue2.0中click点击当前li实现动态切换class
Jun 21 Javascript
JavaScript使用indexOf()实现数组去重的方法分析
Sep 04 Javascript
Vuex的actions属性的具体使用
Apr 14 Javascript
vue 组件简介
Jul 31 Javascript
原生js实现弹窗消息动画
Nov 20 Javascript
html5以及jQuery实现本地图片上传前的预览代码实例讲解
Mar 01 jQuery
非常实用的jQuery代码段集锦【检测浏览器、滚动、复制、淡入淡出等】
Aug 08 #jQuery
微信小程序嵌入腾讯视频源过程详解
Aug 08 #Javascript
17道题让你彻底理解JS中的类型转换
Aug 08 #Javascript
微信小程序bindtap事件与冒泡阻止详解
Aug 08 #Javascript
vue2 拖动排序 vuedraggable组件的实现
Aug 08 #Javascript
React+TypeScript+webpack4多入口配置详解
Aug 08 #Javascript
JavaScript:ES2019 的新特性(译)
Aug 08 #Javascript
You might like
BBS(php &amp; mysql)完整版(七)
2006/10/09 PHP
php自动更新版权信息显示的方法
2015/06/19 PHP
jQuery+PHP发布的内容进行无刷新分页(Fckeditor)
2015/10/22 PHP
javascript 常用代码技巧大收集
2009/02/25 Javascript
基于jquery的15款幻灯片插件
2011/04/10 Javascript
通过js动态操作table(新增,删除相关列信息)
2012/05/23 Javascript
浏览器兼容console对象的简要解决方案分享
2013/10/24 Javascript
Javascript高级技巧分享
2014/02/25 Javascript
JS获得浏览器版本和操作系统版本的例子
2014/05/13 Javascript
推荐9款炫酷的基于jquery的页面特效
2014/12/07 Javascript
node.js中的fs.createWriteStream方法使用说明
2014/12/17 Javascript
javascript数组去重方法汇总
2015/04/23 Javascript
jQuery插件dataTables添加序号列的方法
2016/07/06 Javascript
AngularJS基础 ng-show 指令简单示例
2016/08/03 Javascript
boostrapTable的refresh和refreshOptions区别浅析
2017/01/22 Javascript
angular.js指令中的controller、compile与link函数的不同之处
2017/05/10 Javascript
Three.js基础学习教程
2017/11/16 Javascript
详解webpack 热更新优化
2018/09/13 Javascript
Vue路由模块化配置的完整步骤
2019/08/14 Javascript
基于Layui自定义模块的使用方法详解
2019/09/14 Javascript
Python切片操作实例分析
2018/03/16 Python
python基于paramiko将文件上传到服务器代码实现
2019/07/08 Python
Python爬虫使用代理IP的实现
2019/10/27 Python
python/Matplotlib绘制复变函数图像教程
2019/11/21 Python
jupyter notebook中新建cell的方法与快捷键操作
2020/04/22 Python
英国虚拟主机服务商:eUKhost
2016/08/16 全球购物
TripAdvisor印尼站:全球领先的旅游网站
2018/03/15 全球购物
英国领先的男装设计师服装独立零售商:Repertoire Fashion
2020/10/19 全球购物
Servlet的实例是在生命周期什么时候创建的?配置servlet最重要的是什么?
2012/05/30 面试题
高校学生干部的自我评价分享
2013/11/04 职场文书
幼儿园托班开学寄语
2014/01/18 职场文书
简洁的英文求职信范文
2014/05/03 职场文书
2015年小学一年级班主任工作总结
2015/05/21 职场文书
2019升学宴主持词范本5篇
2019/10/09 职场文书
MySQL注入基础练习
2021/05/30 MySQL
JS前端轻量fabric.js系列物体基类
2022/08/05 Javascript