从零撸一个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的一些注意
Dec 06 Javascript
jQuery AJAX回调函数this指向问题
Feb 08 Javascript
juery框架写的弹窗效果适合新手
Nov 27 Javascript
jscript读写二进制文件的方法
Apr 22 Javascript
JSON与XML优缺点对比分析
Jul 17 Javascript
BootStrap 动态表单效果
Jun 02 Javascript
bootstrap3中container与container_fluid外层容器的区别讲解
Dec 04 Javascript
浅谈AngularJS中$http服务的简单用法
May 15 Javascript
vue鼠标移入添加class样式,鼠标移出去除样式(active)实现方法
Aug 22 Javascript
vue.js的双向数据绑定Object.defineProperty方法的神奇之处
Jan 18 Javascript
详解基于electron制作一个node压缩图片的桌面应用
Jan 29 Javascript
canvas多重阴影发光效果实现
Apr 20 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下一个非常全面获取图象信息的函数
2008/11/20 PHP
Session的工作机制详解和安全性问题(PHP实例讲解)
2014/04/10 PHP
php版微信公众平台回复中文出现乱码问题的解决方法
2016/09/22 PHP
php运行报错Call to undefined function curl_init()的最新解决方法
2016/11/20 PHP
php中preg_replace正则替换用法分析【一次替换多个值】
2017/01/17 PHP
php常用字符串长度函数strlen()与mb_strlen()用法实例分析
2019/06/25 PHP
PHP获取类私有属性的3种方法
2020/09/10 PHP
JavaScript可否多线程? 深入理解JavaScript定时机制
2012/05/23 Javascript
jquery select动态加载选择(兼容各种浏览器)
2013/02/01 Javascript
jquery控制左右箭头滚动图片列表的实例
2013/05/20 Javascript
jquery获取元素索引值index()示例
2014/02/13 Javascript
JQuery设置时间段下拉选择实例
2014/12/30 Javascript
jQuery实现简单下拉导航效果
2015/09/07 Javascript
前端设计师们最常用的JS代码汇总
2016/09/25 Javascript
BootStrap Validator 版本差异问题导致的submitHandler失效问题的解决方法
2016/12/01 Javascript
vue+socket.io+express+mongodb 实现简易多房间在线群聊示例
2017/10/21 Javascript
基于datepicker定义自己的angular时间组件的示例
2018/03/14 Javascript
nodejs简单访问及操作mysql数据库的方法示例
2018/03/15 NodeJs
Vue使用vux-ui自定义表单验证遇到的问题及解决方法
2018/05/10 Javascript
通过实例解析JavaScript常用排序算法
2020/09/02 Javascript
Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
2014/06/04 Python
linecache模块加载和缓存文件内容详解
2018/01/11 Python
Python3导入CSV文件的实例(跟Python2有些许的不同)
2018/06/22 Python
Python简单处理坐标排序问题示例
2019/07/11 Python
python线程中的同步问题及解决方法
2019/08/29 Python
Python socket连接中的粘包、精确传输问题实例分析
2020/03/24 Python
如何基于Python爬虫爬取美团酒店信息
2020/11/03 Python
财务总监管理岗位职责
2014/03/08 职场文书
保险内勤岗位职责
2014/04/05 职场文书
爱我中华演讲稿
2014/05/20 职场文书
物资采购方案
2014/06/12 职场文书
实习护士自荐信
2014/06/21 职场文书
四风问题个人自查剖析材料思想汇报
2014/09/21 职场文书
2019年大学毕业生个人自我鉴定范文大全
2019/03/21 职场文书
委托开发合同书(标准版)
2019/08/07 职场文书
使用CSS实现一个搜索引擎的原理解析
2021/09/25 HTML / CSS