element-ui input组件源码学习

2018年11月25日Web前端0

学习了很多input组件的前置内容,下面就要开始学习下input组件了。

一、html部分

input部分

整个组件是在一个大的div之中的,看下这个div下的几个属性,

:class="[
type === 'textarea' ? 'el-textarea' : 'el-input',
inputSize ? 'el-input--' + inputSize : '',
{
'is-disabled': inputDisabled,
'el-input-group': $slots.prepend || $slots.append,
'el-input-group--append': $slots.append,
'el-input-group--prepend': $slots.prepend,
'el-input--prefix': $slots.prefix || prefixIcon,
'el-input--suffix': $slots.suffix || suffixIcon || clearable
}
]"
@mouseenter="hovering = true"
@mouseleave="hovering = false"

type表示当前是textarea或input,他们对应了不同的样式属性,inputSize则决定了当前组件的大小,对象那个之中,is-disabled决定了当前组件是否是disabled状态,剩下五个属性都是用于修改样式,配合显示头部与尾部组件的。

再加mouseenter和mouseleave事件,用于修改表示鼠标是否再input上方的变量。(这两个事件是不冒泡的)

往内部是一个template,用于当是input时,显示内部的元素。此处使用template可以减少一层元素嵌套,我们都知道template在渲染到dom中时,是没有这层元素的。

在往内层,有个样式是el-input-group__prepend的div,该元素表示了组件的前置内容,如

当然也可以和官网上一样,放select元素。

随后是input元素,直接能看懂的简单属性就不说了,看下几个特殊的属性吧。

  • v-bind="$attrs"

该属性是用于将父辈组件传入的未使用的参数继续传给子组件。而js中的inheritAttrs属性是配合一起使用的,表示是否不会把未被注册的props呈现为普通的HTML属性。

  • composition*事件

input上有关composition的事件可以查看我的另一篇文章:js复合事件

接着是一个span元素,放置用于显示输入框内前部内容的元素。name="prefix"的槽元素是放置了显示文字,i元素则是防止prefix-icon为class的icon图标。该元素用绝对定位移动到输入框前防,而input则设置左padding,让文字和图标不重合。 后面是用于显示再输入框中内部后方的元素块。先看下类是el-input__suffix-inner的span元素,name="suffix"的槽元素用于显示输入框内后部的文字,而i是icon的显示。如:

而包含el-input__clear的类名i元素与尾部的元素只能优先显示一种,当我们在输入内容的时候,就会显示这个清空输入框的查查按钮。 而随后的i元素则是配合表单验证时,检测输入验证错误提示的。

input组件最后一个div元素,用于显示该组件的后置图标,如:

官网上也提供了增加button的方式。

textarea部分

与input部分基本一致,不多说了。

二、JS部分

emitter的可以看这篇:element-ui-emitter-js文件源码学习

migrating的可以看这篇:element-ui focus.js和migrating.js文件源码学习

calcTextareaHeight的可以看这篇:element-ui calcTextareaHeight.js文件源码学习

merge和shared的可以看这篇:element-ui merge.js和shared.js文件源码学习

直接看export的对象,

name

名称

componentName

组件名称

mixins

混入emite事件和migrating。

inheritAttrs

前面也讲到了,和$attrs一起使用,用来表示是否不会把未被注册的props呈现为普通的HTML属性,使得这些属性可以被子组件继承。

inject

注入elForm和elFormItem对象,配合form一起使用,防止对象为空,报错。

data()

currentValue

currentValue表示当前组件的value值,其中用三目运算符,用于处理一些异常值。内部的一个仅用于保存当前值的变量。

textareaCalcStyle

用于存放textarea计算style样式。

hovering

鼠标是否悬浮在组件上方。

focused

组件是否已经聚焦。

isOnComposition

是否正在修改中。

valueBeforeComposition

使用输入法时,记录修改前的值。

props

value

初始时传入的值。

size

输入框尺寸,非textarea时生效。

resize

控制是否能被用户缩放。

form

留着,没看出有啥用。。

disabled

组件是否禁用。

readonly

组件是否只读,原生属性。

type

input的类型(可能是textarea)。

autosize

自适应内容高度,当是 textarea 时,可传入对象,如{ minRows: 2, maxRows: 6 },表示textarea可拖动的最大最小范围。

autocomplete

原生属性,自动补全。

autoComplete

原生属性,自动补全,但此属性要被替换,用 autocomplete 代替他。

validateEvent

输入时是否触发表单的校验。

suffixIcon

输入框头部图标。

prefixIcon

输入框尾部图标。

label

输入框关联的label文字。

clearable

是否可清空。

tabindex

输入框的tabindex,原生属性。

computed

_elFormItemSize()

fromitem大小。

validateState()

组件的当前状态,可以修改icon的样式。

needStatusIcon()

是否要显示状态的图标。

validateIcon()

输入验证,错误的图标样式。

textareaStyle()

合并textarea的style样式,并显示。

inputSize()

input组件的大小,优先级 本生 > formitem。

inputDisabled()

input组件是否是disabled状态。

showClear()

是否显示清空输入框的叉叉按钮,判断条件比较多,首先要支持显示,不是只读和禁用状态,当前值不能是空的,并且当前已经聚焦或者处于鼠标悬浮状态。

watch

value(val, oldValue)

监听value变化,触发修改currentvalue。

methods

focus()

聚焦表单输入元素。

blur()

表单输入元素失焦。

getMigratingConfig()

提示迁移的属性。

handleBlur(event)

元素失焦处理函数,

// 重置状态
this.focused = false;
// 触发实例上的blur事件
this.$emit('blur', event);

select()

选中对应的输入元素,会触发元素上的focus事件。

(this.$refs.input || this.$refs.textarea).select();

利用ref选取元素,并选中该元素。

resizeTextarea()

重新刷新 textarea ,重新计算样式。

if (this.$isServer) return;
if (type !== 'textarea') return;

服务器渲染或非textarea时,不去计算。

当不设置autosize时,则直接使用calcTextareaHeight,否则传入minRows与maxRows计算。

handleFocus(event)

focus时的处理函数。

// 重置状态
this.focused = true;
// 触发实例上的focus事件
this.$emit('focus', event);

handleComposition(event)

当输入法结束后,进行变量的处理,并取触发input修改。

// 输入法结束,重装composition状态
this.isOnComposition = false;
// 保存currentValue
this.currentValue = this.valueBeforeComposition;
this.valueBeforeComposition = null;
// 触发input修改
this.handleInput(event);

其他状态时,获取最后一个字符,这儿对korean字体做了特别判断,korean的坑先留着。 当时start时,先保留先前的变量。

if (this.isOnComposition && event.type === 'compositionstart') {
    // 刚开始输入时,提前保存输入法启动前的值
    this.valueBeforeComposition = text;
}

handleInput(event)

先看下该函数的调用时刻, 修改value值,直接的键盘输入(非输入法),会直接触发 handleInput 函数,如果是输入法输入,将会在composition**之后才触发 handleInput 函数。

const value = event.target.value;
this.setCurrentValue(value);
// 正在修改输入法时,不去处理
if (this.isOnComposition) return;
// 修改组件的model值
this.$emit('input', value);

handleChange(event)

用于触发实例的change事件。

setCurrentValue(value)

首先,编辑中并且当前值与修改值没有变化(输入法输入后,没确认,又删除了),不做处理。 随后将value赋值给currentValue,正在编辑中的也不做处理。此处使用了$nextTick做处理,

// 第一次由handleInput函数调入,此时value值(model)未修改,所以使用了nextTick处理
// 第二次进入是因为watch了value的变化,此时value与currentValue相同,会触发后面的dispatch
this.$nextTick(this.resizeTextarea);

calcIconOffset(place)

用于计算图标的偏移量。 一上来使用slice方法,并用call将nodeList转成正常的数组。空数组不做处理。

// 过滤无效的,看了,似乎没啥用,因为elList选出的元素的parentNode都是组件本身
for (let i = 0; i < elList.length; i++) {
    if (elList[i].parentNode === this.$el) {
        el = elList[i];
        break;
    }
}

随后使用translate设置图标的位置,

if (this.$slots[pendant]) {
    // 当存在输入框外部的前后元素时,需要计算输入框内图标与文字的位置,并用translate动画加以移动开
    // 个人感觉在这是不是可以重新计算下输入框的右padding值,使得输入框内的文字不会与输入内容重合
    el.style.transform = `translateX(${place === 'suffix' ? '-' : ''}${this.$el.querySelector(`.el-input-group__${pendant}`).offsetWidth}px)`;
} else {
    // 删除增加过的style样式
    el.removeAttribute('style');
}

updateIconOffset()

更新图标偏移量,计算prefix和suffix图标。

clear()

点击输入框内的清空按钮触发。

// 设置组件value值为空
this.$emit('input', '');
// 触发实例上的change事件
this.$emit('change', '');
// 触发实例上的clear事件
this.$emit('clear');
// 清空输入框中的值,置为空字符串
this.setCurrentValue('');
this.focus();

created()

// 绑定inputSelect事件
this.$on('inputSelect', this.select);

mounted()

挂载成功后处理函数,resize了textarea和计算了icon的位置。

updated()

每次组件值有更新时,触发icon更新。 代码地址:input.vue