vant-weapp中stepper源码阅读

474 阅读4分钟

Stepper

首先我运行小程序模式时是出现了问题的,有报错,错误如下:

1662175141(1).png 然后我使用vscode看了一下:

1662175213(1).png

这句报错了,然后我把devtools直接改成了windows,然后就可以npm run dev成功了,在example下的dist就生成成功了。

1662175455(1).png 然后依照步骤在微信开发工具中就可以看到如下界面

image.png

由于是一个组件,源码分为三个部门即wxml(布局), js, wxss(样式,不进行叙述): wxml:

<wxs src="../wxs/utils.wxs" module="utils" />
<wxs src="./index.wxs" module="computed" />

<view class="{{ utils.bem('stepper', [theme]) }} custom-class">
  <view
    wx:if="{{ showMinus }}"
    data-type="minus"
    style="{{ computed.buttonStyle({ buttonSize }) }}"
    class="minus-class {{ utils.bem('stepper__minus', { disabled: disabled || disableMinus || currentValue <= min }) }}"
    hover-class="van-stepper__minus--hover"
    hover-stay-time="70"
    bind:tap="onTap"
    bind:touchstart="onTouchStart"
    bind:touchend="onTouchEnd"
  >
    <slot name="minus" />
  </view>
  <input
    always-embed="{{ false }}"
    type="{{ integer ? 'number' : 'digit' }}"
    class="input-class {{ utils.bem('stepper__input', { disabled: disabled || disableInput }) }}"
    style="{{ computed.inputStyle({ buttonSize, inputWidth }) }}"
    value="{{ currentValue }}"
    focus="{{ focus }}"
    disabled="{{ disabled || disableInput }}"
    always-embed="{{ alwaysEmbed }}"
    bindinput="onInput"
    bind:focus="onFocus"
    bind:blur="onBlur"
  />
  <view
    wx:if="{{ showPlus }}"
    data-type="plus"
    style="{{ computed.buttonStyle({ buttonSize }) }}"
    class="plus-class {{ utils.bem('stepper__plus', { disabled: disabled || disablePlus || currentValue >= max }) }}"
    hover-class="van-stepper__plus--hover"
    hover-stay-time="70"
    bind:tap="onTap"
    bind:touchstart="onTouchStart"
    bind:touchend="onTouchEnd"
  >
    <slot name="plus" />
  </view>
</view>

从上面可以看出view和html使用的div基本是无区别的,写法也是和vue中的页面写法非常相似(本人只学习了vue,对于其他不熟,所以基本只会针对vue进行对比)。 其中包含两个view,一个input,两个view是步进器前后的加减按钮,input是中间的数字框,而且针对加减按钮添加了name为plus和minus的slot,可以对两个按钮进行扩展(此在官方文档中没有提及)。

js:

import { VantComponent } from '../common/component';
import { isDef } from '../common/validator';
const LONG_PRESS_START_TIME = 600;
const LONG_PRESS_INTERVAL = 200;
function add(num1, num2) {
    const cardinal = Math.pow(10, 10);
    return Math.round((num1 + num2) * cardinal) / cardinal;
}
function equal(value1, value2) {
    return String(value1) === String(value2);
}
VantComponent({
    field: true,
    classes: ['input-class', 'plus-class', 'minus-class'],
    props: {
        value: {
            type: null,
            observer: 'observeValue',
        },
        integer: {
            type: Boolean,
            observer: 'check',
        },
        disabled: Boolean,
        inputWidth: String,
        buttonSize: String,
        asyncChange: Boolean,
        disableInput: Boolean,
        decimalLength: {
            type: Number,
            value: null,
            observer: 'check',
        },
        min: {
            type: null,
            value: 1,
            observer: 'check',
        },
        max: {
            type: null,
            value: Number.MAX_SAFE_INTEGER,
            observer: 'check',
        },
        step: {
            type: null,
            value: 1,
        },
        showPlus: {
            type: Boolean,
            value: true,
        },
        showMinus: {
            type: Boolean,
            value: true,
        },
        disablePlus: Boolean,
        disableMinus: Boolean,
        longPress: {
            type: Boolean,
            value: true,
        },
        theme: String,
        alwaysEmbed: Boolean,
    },
    data: {
        currentValue: '',
    },
    created() {
        this.setData({
            currentValue: this.format(this.data.value),
        });
    },
    methods: {
        observeValue() {
            const { value, currentValue } = this.data;
            if (!equal(value, currentValue)) {
                this.setData({ currentValue: this.format(value) });
            }
        },
        check() {
            const val = this.format(this.data.currentValue);
            if (!equal(val, this.data.currentValue)) {
                this.setData({ currentValue: val });
            }
        },
        isDisabled(type) {
            const { disabled, disablePlus, disableMinus, currentValue, max, min, } = this.data;
            if (type === 'plus') {
                return disabled || disablePlus || currentValue >= max;
            }
            return disabled || disableMinus || currentValue <= min;
        },
        onFocus(event) {
            this.$emit('focus', event.detail);
        },
        onBlur(event) {
            const value = this.format(event.detail.value);
            this.emitChange(value);
            this.$emit('blur', Object.assign(Object.assign({}, event.detail), { value }));
        },
        // filter illegal characters
        filter(value) {
            value = String(value).replace(/[^0-9.-]/g, '');
            if (this.data.integer && value.indexOf('.') !== -1) {
                value = value.split('.')[0];
            }
            return value;
        },
        // limit value range
        format(value) {
            value = this.filter(value);
            // format range
            value = value === '' ? 0 : +value;
            value = Math.max(Math.min(this.data.max, value), this.data.min);
            // format decimal
            if (isDef(this.data.decimalLength)) {
                value = value.toFixed(this.data.decimalLength);
            }
            return value;
        },
        onInput(event) {
            const { value = '' } = event.detail || {};
            // allow input to be empty
            if (value === '') {
                return;
            }
            let formatted = this.filter(value);
            // limit max decimal length
            if (isDef(this.data.decimalLength) && formatted.indexOf('.') !== -1) {
                const pair = formatted.split('.');
                formatted = `${pair[0]}.${pair[1].slice(0, this.data.decimalLength)}`;
            }
            this.emitChange(formatted);
        },
        emitChange(value) {
            if (!this.data.asyncChange) {
                this.setData({ currentValue: value });
            }
            this.$emit('change', value);
        },
        onChange() {
            const { type } = this;
            if (this.isDisabled(type)) {
                this.$emit('overlimit', type);
                return;
            }
            const diff = type === 'minus' ? -this.data.step : +this.data.step;
            const value = this.format(add(+this.data.currentValue, diff));
            this.emitChange(value);
            this.$emit(type);
        },
        longPressStep() {
            this.longPressTimer = setTimeout(() => {
                this.onChange();
                this.longPressStep();
            }, LONG_PRESS_INTERVAL);
        },
        onTap(event) {
            const { type } = event.currentTarget.dataset;
            this.type = type;
            this.onChange();
        },
        onTouchStart(event) {
            if (!this.data.longPress) {
                return;
            }
            clearTimeout(this.longPressTimer);
            const { type } = event.currentTarget.dataset;
            this.type = type;
            this.isLongPress = false;
            this.longPressTimer = setTimeout(() => {
                this.isLongPress = true;
                this.onChange();
                this.longPressStep();
            }, LONG_PRESS_START_TIME);
        },
        onTouchEnd() {
            if (!this.data.longPress) {
                return;
            }
            clearTimeout(this.longPressTimer);
        },
    },
});


针对js的分析:

首先写法上是与vue2的版本非常的相似,基本无差别。

源码中针对0.1 + 0.2 = 0.30000000000000004的问题考虑的很细致,将此种情况进行了处理。(产生原因:JS 采用的是双精度版本, 即IEEE 754 双精度版本(64位),计算机的信息全部转化为二进制进行存储的,那么0.1的二进制表示的是一个无限循环小数,该版本的 JS 采用的是浮点数标准需要对这种无限循环的二进制进行截取,从而导致了精度丢失,造成了0.1不再是0.1,截取之后0.1变成了 0.100…001,0.2变成了0.200…002。所以两者相加的数大于0.3)。而且源码中针对值的比较都转换成了string类型的数据进行比较,不是针对number的数据进行比较(猜测应该是避免精度丢失的问题)。

  1. 开始初始化先进行了observeValue函数调用,给currentValue赋予初始值,再在created中对currentValue再一次进行了赋值。赋值时使用了format函数,format函数中调用了filter函数,将字符串中非数字的东西通过正则给过滤掉(严谨),并且会根据是否是整型来去除小数位(严谨)。
  2. 然后开始功能的讲解,最基础的数值的加减,主要通过onTap函数实现,首先根据event中获取按钮的类型,调用onChanage函数,onChange函数会根据按钮的状态判断是否触发overlimit事件,按钮的状态有可能虽然都是不可点击状态,但是有可能是设置disabled状态,或者单独针对一个按钮设置的状态,甚至是由于值超出范围引发的不可点击状态,然后根据步长计算input框的值,先调用emitChange函数触发change事件,在emitChange中还会判断是否需要异步设置值(没有搞懂这个异步设置的含义),在根据按钮类型触发对应的事件。
  3. 接着是针对input框的输入值的变化,调用的是onInput函数,onInput函数中调用了之前讲过的format函数与filter函数进行了值的清洗,然后使用了isDef函数校验了保留小数位这个参数是否存在,存在时进行了处理,最后调用了emitChange函数。
  4. 其他的功能包括长按,输入框聚焦/失焦等功能基本都是基于上述讲的三点进行,基本读完上述的三点,后续的功能基本也就了解。