从零撸一个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 相关文章推荐
jQuery 白痴级入门教程
Nov 11 Javascript
使用js简单实现了tree树菜单
Nov 20 Javascript
jQuery实现鼠标滑过链接控制图片的滑动展开与隐藏效果
Oct 28 Javascript
javascript中利用柯里化函数实现bind方法【推荐】
Apr 29 Javascript
AngularJS中比较两个数组是否相同
Aug 24 Javascript
jQuery中的100个技巧汇总
Dec 15 Javascript
原生js实现商品放大镜效果
Jan 12 Javascript
使用react-router4.0实现重定向和404功能的方法
Aug 28 Javascript
利用javascript如何随机生成一定位数的密码
Sep 22 Javascript
vue 项目如何引入微信sdk接口的方法
Dec 18 Javascript
Koa 使用小技巧(小结)
Oct 22 Javascript
基于vue和websocket的多人在线聊天室
Feb 01 Javascript
非常实用的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
深入PHP异步执行的详解
2013/06/03 PHP
PHP实现对文件锁进行加锁、解锁操作的方法
2017/07/04 PHP
php简单中奖算法(实例)
2017/08/15 PHP
laravel实现简单用户权限的示例代码
2019/05/28 PHP
详解PHP 7.4 中数组延展操作符语法知识点
2019/07/19 PHP
php引用和拷贝的区别知识点总结
2019/09/23 PHP
不能再简单的无闪刷新验证码原理很简单
2007/11/05 Javascript
快速排序 php与javascript的不同之处
2011/02/22 Javascript
jquery中常用的SET和GET$(”#msg”).html循环介绍
2013/10/09 Javascript
jquery获取对象的方法足以应付常见的各种类型的对象
2014/05/14 Javascript
CSS3实现动态背景登录框的代码
2015/07/28 Javascript
JS动态创建元素的两种方法
2016/04/20 Javascript
AngularJS 指令详细介绍
2016/07/27 Javascript
JS图片放大效果简单实现代码
2016/09/08 Javascript
JQuery Dialog对话框 不能通过Esc关闭的原因分析及解决办法
2017/01/18 Javascript
vue+vue-validator 表单验证功能的实现代码
2017/11/13 Javascript
小程序分享模块超级详解(推荐)
2019/04/10 Javascript
layui添加动态菜单与选项卡 AJAX请求的例子
2019/09/25 Javascript
vue开发移动端底部导航条功能
2020/04/08 Javascript
Python中使用Inotify监控文件实例
2015/02/14 Python
Python实现按照指定要求逆序输出一个数字的方法
2018/04/19 Python
解决tensorflow测试模型时NotFoundError错误的问题
2018/07/27 Python
Python3 itchat实现微信定时发送群消息的实例代码
2019/07/12 Python
python如何实现不可变字典inmutabledict
2020/01/08 Python
利用CSS3实现单选框动画特效示例代码
2016/09/26 HTML / CSS
极简的HTML5模版
2015/07/09 HTML / CSS
HTML5自定义mp3播放器源码
2020/01/06 HTML / CSS
荷兰街头时尚之家:Funkie House
2019/03/18 全球购物
预备党员转正考核材料
2014/06/03 职场文书
领导干部保密承诺书
2014/08/30 职场文书
庆六一宣传标语
2014/10/08 职场文书
大学生就业推荐表自我评价
2015/03/02 职场文书
财政局长个人总结
2015/03/04 职场文书
办公室主任岗位职责范本
2015/03/31 职场文书
三十年再续同学情倡议书
2019/11/27 职场文书
六种css3实现的边框过渡效果
2021/04/22 HTML / CSS