从零撸一个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 相关文章推荐
js关闭子窗体刷新父窗体实现方法
Dec 04 Javascript
JavaScript字符串对象split方法入门实例(用于把字符串分割成数组)
Oct 16 Javascript
JavaScript中的异常捕捉介绍
Dec 31 Javascript
JavaScript解八皇后问题的方法总结
Jun 12 Javascript
require简单实现单页应用程序(SPA)
Jul 12 Javascript
JS动态计算移动端rem的解决方案
Oct 14 Javascript
Javascript中构造函数要注意的一些坑
Jan 23 Javascript
js实现简单的手风琴效果
Feb 27 Javascript
vue实现导航栏效果(选中状态刷新不消失)
Dec 13 Javascript
使用vue打包时vendor文件过大或者是app.js文件很大的问题
Jun 29 Javascript
详解vue2.0模拟后台json数据
May 16 Javascript
layui表格分页 记录勾选的实例
Sep 02 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
DOMXML函数笔记
2006/10/09 PHP
实用函数10
2007/11/08 PHP
PHP实现163邮箱自动发送邮件
2016/03/29 PHP
本地图片预览(支持IE6/IE7/IE8/Firefox3)经验总结
2013/03/25 Javascript
jQuery带箭头提示框tooltips插件集锦
2014/11/17 Javascript
jQuery中is()方法用法实例
2015/01/06 Javascript
2则自己编写的jQuery特效分享
2015/02/26 Javascript
Javascript函数式编程简单介绍
2015/10/11 Javascript
jQuery操作基本控件方法实例分析
2015/12/31 Javascript
JS、jQuery中select的用法详解
2016/04/21 Javascript
jQuery Easy UI中根据第一个下拉框选中的值设置第二个下拉框是否可以编辑
2016/11/29 Javascript
浅谈jQuery中事情的动态绑定
2017/02/12 Javascript
bootstrap+jQuery 实现下拉菜单中复选框全选和全不选效果
2017/06/12 jQuery
Vue开发之封装分页组件与使用示例
2019/04/25 Javascript
javascript设计模式之迭代器模式
2020/01/30 Javascript
javascript 易错知识点实例小结
2020/04/25 Javascript
react基本安装与测试示例
2020/04/27 Javascript
python自动裁剪图像代码分享
2017/11/25 Python
python爬虫获取多页天涯帖子
2018/02/23 Python
python获取文件真实链接的方法,针对于302返回码
2018/05/14 Python
老生常谈python中的重载
2018/11/11 Python
基于Python的Post请求数据爬取的方法详解
2019/06/14 Python
python使用SQLAlchemy操作MySQL
2020/01/02 Python
python 轮询执行某函数的2种方式
2020/05/03 Python
曼联官方网上商店:Manchester United Direct
2017/07/28 全球购物
台湾家适得:Homeget
2019/02/11 全球购物
第一批党的群众路线教育实践活动工作总结
2014/03/03 职场文书
学校三八妇女节活动情况总结
2014/03/09 职场文书
网络工程专业自荐信范文
2014/03/16 职场文书
素质教育标语
2014/06/27 职场文书
施工员岗位职责
2015/02/10 职场文书
城管个人总结
2015/02/28 职场文书
使用Djongo模块在Django中使用MongoDB数据库
2021/06/20 Python
能让Python提速超40倍的神器Cython详解
2021/06/24 Python
BCL经典机 SONY ICF-5900W电路分析
2022/04/24 无线电
nginx lua 操作 mysql
2022/05/15 Servers