本集定位:
听到计数器这个名字不少人是否是一瞬间没有什么印象, 毕竟这个组件用的比较少,就是那种左边一个'-'右边一个'+', 控制某些数量的时候才会用到, 好比我以前作的商城小程序只有'下单'页面的规格弹出框里面才有他的身影, 若是是涉及处处理商品数量很频繁的业务场景应该会很常见吧, 可是不要看这个组件小, 编写它的时候坑还很多, 本次咱们就来作一个计数器, 目标就是尽量小, 尽量的省性能.css
先展现一章普通状态的图, 让咱们更直观的去完成它, 造型比较别致, 是本套组件的一个特色, 哈哈作的与别人同样会致使思想的禁锢, 本身写代码多尝试新的东西, 可是工做中必定要中规中矩, 以公司条款为准则.前端
vue-cc-ui/src/components/InputNumber/index.jsvue
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.vuegit
<template> <div class="cc-input-number"> // 左侧的'➖'符号 <div class="cc-input-number__reduce"> // 本身封装的icon组件, 出镜率还挺高😼. <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里面, 且同级别这种方式, 与其余的不太同样, 由于这样更直观, 并且也足够实现我想要的功能.github
// 减小 <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的相关代码.web
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 方法的实现app
reduce() { let num = Number(this.value) - this.step; this.emitVal(num); },
监听input框的输入事件dom
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下面会解释👇 if (value === this.oldVal) return; this.oldVal = ls; // 发出两个事件, 一个负责改变value, 一个负责返回给用户 // 毕竟用户不可能监听input事件而后再把值附上去, 太麻烦 this.$emit("input", value); this.$emit("change", value); // 这一步很重要 // 下面会详细说 this.$refs.input.value = value; }
上面遗留的问题,这里解释一下.
上面的两个问题都是涉及到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的, 因此很早就有人剔除双向绑定的坏处, 封装越多的组件感受就越明显.
在计算属性里面咱们队当前值进行了监控, 返回的是置灰的颜色, 这个让用户自定的意义不大, 因此直接写了.
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; } }
效果展现
end
总的来讲是这些组件中比较简单的一个了, 有些坑可以让我更好的学习vue以及前端的思想, 总的来讲挺有趣的.
你们继续一块儿学习,一块儿进步, 早日实现自我价值!!
下一集准备聊聊 tab切换组件的相关知识;
github:连接描述
我的博客: 连接描述