携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情
简介
输入框组件 Input
通过鼠标或键盘输入表单域内容,提供复合型型输入框,带搜索的输入框,还可以进行大小选择。 本文将分析其源码实现,耐心读完,相信会对您有所帮助。🔗 组件文档 Input 🔗 gitee源码
为了更好的理解本文,请先阅读以下文章
更多组件分析详见 👉 📚 Element UI 源码剖析组件总览 。
本专栏的 gitbook
版本地址已经发布 📚《learning element-ui》 ,内容同步更新中!
模板HTML
组件的 props
声明,各属性功能描述详见官方文档Input#attributes 。
组件根元素是一个类名为el-textarea
或el-input
的 div 元素,元素内容包含了三部分:
- 封装原生
input
控件实现单行文本输入框。 - 封装原生
textarea
控件多行文本输入文本域。
// packages\input\src\input.vue
<template>
<div :class="[type === 'textarea' ? 'el-textarea' : 'el-input',]" >
<!-- 单行文本输入框 -->
<template v-if="type !== 'textarea'">
<!-- 输入框前置内容 -->
<div class="el-input-group__prepend"></div>
<!-- 表单输入控件 -->
<input>
<!-- 输入框头部内容 -->
<span class="el-input__prefix"></span>
<!-- 输入框尾部内容 -->
<span class="el-input__suffix"></span>
<!-- 输入框后置内容 -->
<div class="el-input-group__append"></div>
</template>
<!-- 多行文本输入的文本域 -->
<textarea></textarea>
</div>
</template>
组件根元素
- 属性
type
值确定使用el-textarea
或el-input
。 - 组件尺寸
inputSize
生成样式el-input--medium/small/mini
。 - 禁用状态
inputDisabled
生成样式is-disabled
。 - 开启输入长度限制,输入超限时
inputExceed
生成样式is-disabled
。 - 复合型输入框样式
el-input-group
、el-input-group--append/prepend
根据前置/后置插槽内容生成。 - 头部/尾部样式
el-input--prefix
、el-input--suffix
根据插槽内容或功能属性值生成。 - 添加了鼠标移入移出事件,使用内部属性
hover
记录元素悬停状态。
<div :class="[
type === 'textarea' ? 'el-textarea' : 'el-input',
inputSize ? 'el-input--' + inputSize : '',
{
'is-disabled': inputDisabled,
'is-exceed': inputExceed,
'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 || showPassword
}
]"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
// ...
</div>
单行文本输入
单行文本输入框通过封装原生 input
控件实现,支持控件的原生属性。组件通过组合以下多种元素实现文本框 text
或密码框password
等复合型输入框功能:
- 输入框前置内容,提供具名插槽
prepend
,内容一般为标签或按钮。 - 原生 input 表单输入控件,添加了自定义事件。
- 输入框头部内容,可以通过
prefix-icon
或具名插槽prefix
增加显示图标。 - 输入框尾部内容,可以通过
suffix-icon
或具名插槽suffix
增加显示图标。该元素也用于展示输入框清空Icon、密码显隐切换Icon以及展示输入字数统计。 - 输入框后置内容,提供具名插槽
append
,内容一般为标签或按钮。
<template v-if="type !== 'textarea'">
<!-- 输入框前置内容 -->
<div class="el-input-group__prepend" v-if="$slots.prepend">
<slot name="prepend"></slot>
</div>
<!-- 表单输入控件 -->
<input
:tabindex="tabindex" // 元素是否可以聚焦 键盘导航
v-if="type !== 'textarea'"
class="el-input__inner"
v-bind="$attrs" // 透传 Attributes 例如 placeholder
:type="showPassword ? (passwordVisible ? 'text': 'password') : type" // text/password 也支持其他原生input的type值
:disabled="inputDisabled" // 是否禁用
:readonly="readonly" // 是否只读
:autocomplete="autoComplete || autocomplete" // 自动补全
ref="input"
@compositionstart="handleCompositionStart" // 输入法编辑器 (IME) 事件
@compositionupdate="handleCompositionUpdate"
@compositionend="handleCompositionEnd"
@input="handleInput" // 输入内容
@focus="handleFocus" // 获取焦点
@blur="handleBlur" // 失去焦点
@change="handleChange" // 输入值变化
:aria-label="label" // ARIA 无障碍属性
>
<!-- 输入框头部内容 -->
<span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">
<slot name="prefix"></slot>
<i class="el-input__icon" v-if="prefixIcon" :class="prefixIcon"></i>
</span>
<!-- 输入框尾部内容 -->
<span class="el-input__suffix" v-if="getSuffixVisible()">
<span class="el-input__suffix-inner">
<template v-if="!showClear || !showPwdVisible || !isWordLimitVisible">
<slot name="suffix"></slot>
<i class="el-input__icon" v-if="suffixIcon" :class="suffixIcon"></i>
</template>
// ...
</span>
// ...
</span>
<!-- 输入框后置内容 -->
<div class="el-input-group__append" v-if="$slots.append">
<slot name="append"></slot>
</div>
</template>
组件渲染效果如下:
输入框头部/尾部都提供了具名插槽用于增加显示图标,当然也可以传入文本等其他内容,但不建议这么做。
当设置头部/尾部内容时, input 输入框通过属性 padding
提供了 30px
的宽度区域用于内容展示。头部/尾部元素使用绝对布局,将内容偏移覆盖至 padding 区域。
.el-input__prefix {
position: absolute;
height: 100%;
left: 5px;
top: 0;
}
.el-input__suffix {
position: absolute;
height: 100%;
right: 5px;
top: 0;
}
.el-input--prefix .el-input__inner {
padding-left: 30px;
}
.el-input--suffix .el-input__inner {
padding-right: 30px;
}
以下示例将自定义文本传入插槽。
<el-input placeholder="请输入内容" v-model="input">
<template slot="prefix">
<span style="display: flex; align-items: center; height: 100%">头部内容</span>
</template>
<template slot="suffix">
<span style="display: flex; align-items: center; height: 100%">尾部内容</span>
</template>
</el-input>
示例渲染出现内容覆盖
input 元素类型
组件默认使用 text
类型的input控件。当设置 showPassword
可得到一个可切换显示隐藏的密码框,内部属性passwordVisible
记录显隐状态,根据不同的状态使用 password
或 text
类型。
:type="showPassword ? (passwordVisible ? 'text': 'password') : type"
属性 type
值也可设置为其他原生input的type值。
<el-input v-model="input" placeholder="请输入内容" type="color"></el-input>
<el-input v-model="input" placeholder="请输入内容" type="date"></el-input>
<el-input v-model="input" placeholder="请输入内容" type="datetime-local"></el-input>
<el-input v-model="input" placeholder="请输入内容" type="file"></el-input>
<el-input v-model="input" placeholder="请输入内容" type="month"></el-input>
<el-input v-model="input" placeholder="请输入内容" type="time"></el-input>
<el-input v-model="input" placeholder="请输入内容" type="week"></el-input>
使用其他原生类型时组件渲染效果,但是一般不建议这么使用!一般组件类库都会提供对应的更加丰富的功能组件,使用库组件页面样式风格更加统一,提升用户交互体验。
输入法编辑器(IME)事件
输入法在中文、日文和韩文等少数语言中使用。以中文拼音输入法为例,输入的过程大致可以分为组字(composition) 和 提交(commit) 两阶段。比如想打“你好”两个字,会在输入框输入“nihao”的拼音,当输入第一个字母“n”时,组字过程就开始了。此时本地的 IME(input method editor) 软件(比如微软/搜狗拼音输入法)会为我们提供组字框和候选列表的 UI 组件。
关于输入法事件更多介绍,请阅读 Web 键盘输入法应用开发指南 —— 输入法事件。
compositionstart
、compositionupdate
和compositionend
是一组事件。
-
首先是使用拼音输入法开始输入汉字时
compositionstart
被触发,组字框和候选列表相应出现; -
此后,每按一个新键,就会触发
compositionupdate
,此时组字框和候选列表的内容也发生变化; -
当选择了候选列表中的某个字或词,或者敲击空格(中文输入法),
compositionend
事件会触发,表明输入被提交。 -
当取消输入时,比如使用鼠标单击页面空白处,就会终止当前输入,也会触发
compositionend
事件。
属性isComposing
值为 true
表示用户正在输入。输入结束后(选择字词或者取消),调用方法handleInput
,触发组件 input
事件。
// @compositionstart="handleCompositionStart"
// @compositionupdate="handleCompositionUpdate"
// @compositionend="handleCompositionEnd"
handleCompositionStart() {
this.isComposing = true; // 正在输入
},
handleCompositionUpdate(event) {
const text = event.target.value;
const lastCharacter = text[text.length - 1] || '';
// 韩文字符编码 判断最后一个字符是否特殊的功能键 Process Key
this.isComposing = !isKorean(lastCharacter);
},
handleCompositionEnd(event) {
// 输入结束后,触发 input 事件
if (this.isComposing) {
this.isComposing = false;
this.handleInput(event);
}
},
input/change 事件
- 当
<input>
、<select>
、<textarea>
元素的value
被修改时,会触发input
事件。 - 当用户更改
<input>
、<select>
、<textarea>
元素的值并提交这个更改时,change
事件在这些元素上触发。基于表单元素的类型和用户对标签的操作的不同,change
事件触发的时机也不同。
对于文本输入元素,比如 <input type="text">
,每当元素的 value
改变,input
事件都会被触,change
事件在控件失去焦点后才会触发。
// @input="handleInput"
// @change="handleChange"
handleInput(event) {
// 输入法下用户正在输入
if (this.isComposing) return;
// IE 11下 DatePicker组件 hack 写法,详见issues
// https://github.com/ElemeFE/element/issues/8548
// should remove the following line when we don't support IE
if (event.target.value === this.nativeInputValue) return;
// 触发触发当前实例上的input事件
this.$emit('input', event.target.value);
// Input 为受控组件 更新组件的绑定值
this.$nextTick(this.setNativeInputValue);
},
handleChange(event) {
// 触发触发当前实例上的change事件
this.$emit('change', event.target.value);
},
对于需要使用输入法的语言, v-model
不会在输入法组合文字过程中得到更新,因为compositionend
事件没触发是不会执行handleInput
逻辑。
if (this.isComposing) return;
focus/blur 事件
元素获取或失去焦点时,调用事件监听方法,更新内部属性focused
记录元素焦点状态,同时触发实例自定义 focus
或 blur
事件。
// @focus="handleFocus"
// @blur="handleBlur"
handleFocus(event) {
this.focused = true;
// 触发触发当前实例上的focus事件
this.$emit('focus', event);
},
handleBlur(event) {
this.focused = false;
// 触发触发当前实例上的blur事件
this.$emit('blur', event);
// 开启输入时是否触发表单的校验
if (this.validateEvent) {
// 触发组件`FormItem`的自定义`el.form.blur`事件。
this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
}
},
provide/inject 依赖注入
在 Form 组件中,每一个表单域由一个 Form-Item 组件构成,表单域中可以放置各种类型的表单控件,包括 Input、Select、Checkbox、Radio、Switch、DatePicker、TimePicker等。
表单form
和表单域form-item
使用provide
选项指定给后代组件的状态,避免了 prop 逐级透传 。
// packages\form\src\form.vue
provide() {
return {
elForm: this
};
},
// packages\form\src\form-item.vue
provide() {
return {
elFormItem: this
};
},
inject: ['elForm'],
computed: {
// ...
_formSize() {
return this.elForm.size;
},
elFormItemSize() {
return this.size || this._formSize;
},
sizeClass() {
return this.elFormItemSize || (this.$ELEMENT || {}).size;
}
},
组件 input
使用inject
选项注入上层组件提供的数据,如尺寸、校验、禁用,用于组件内部状态的控制计算。
// packages\input\src\input.vue
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
computed: {
// 表单域下组件的尺寸
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
// 表单域下组件的校验状态 校验中/成功/失败
validateState() {
return this.elFormItem ? this.elFormItem.validateState : '';
},
// 是否在输入框中显示校验结果反馈图标
needStatusIcon() {
return this.elForm ? this.elForm.statusIcon : false;
},
// 表单域下组件的校验状态图标
validateIcon() {
return {
validating: 'el-icon-loading',
success: 'el-icon-circle-check',
error: 'el-icon-circle-close'
}[this.validateState];
},
// 组件尺寸大小
inputSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
// 组件禁用状态
inputDisabled() {
return this.disabled || (this.elForm || {}).disabled;
},
}
// this.$ELEMENT 来源于组件库的全局注册
const install = function(Vue, opts = {}) {
Vue.prototype.$ELEMENT = {
size: opts.size || '',
zIndex: opts.zIndex || 2000
};
// ...
};
由于篇幅原因,内容就到这里了。下文将详细介绍组件的生命周期。
📚参考&关联阅读
"表单输入绑定",vuejs
"input_event",MDN
"change_event",MDN
关注专栏
如果本文对您有所帮助请关注➕、 点赞👍、 收藏⭐!您的认可就是对我的最大支持!
此文章已收录到专栏中 👇,可以直接关注。