element-ui源码详细分析以及在其中能够学到的东西整理。(有问题欢迎指正与讨论 也能够来小站逛逛)javascript
created() {
// 与select组件相关联 (若select组件已发布inputSelect事件则触发选中)
this.$on('inputSelect', this.select);
},
mounted() {
// 动态文本域(高度)
this.resizeTextarea();
// 前置后置元素偏移(样式)
this.updateIconOffset();
},
updated() {
// 视图重绘完毕后 前置后置偏移(样式)
this.$nextTick(this.updateIconOffset);
}
复制代码
插槽及一些props传入的参数控制外层样式html
<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" >
<!-- 内部被分为 input结构 与 textarea结构 -->
</div>
<!-- 动态class 具名插槽 $slots.prepend: 前置插槽 $slots.append: 后置插槽 $slots.prefix: 前置icon插槽 $slots.suffix: 后置icon插槽 不使用插槽的icon prefixIcon: 前置icon suffixIcon: 后置icon clearable: 后置是否清空 -->
复制代码
<!-- 输入框结构 -->
<template v-if="type !== 'textarea'">
<!-- 前置元素 -->
<div class="el-input-group__prepend" v-if="$slots.prepend">
...
</div>
<input :tabindex="tabindex" v-if="type !== 'textarea'" class="el-input__inner" v-bind="$attrs" :type="type" :disabled="inputDisabled" :readonly="readonly" :autocomplete="autoComplete" :value="currentValue" ref="input" @compositionstart="handleComposition" @compositionupdate="handleComposition" @compositionend="handleComposition" @input="handleInput" @focus="handleFocus" @blur="handleBlur" @change="handleChange" :aria-label="label" >
<!-- 前置内容 -->
<span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">
...
</span>
<!-- 后置内容 -->
<span class="el-input__suffix" v-if="$slots.suffix || suffixIcon || showClear || validateState && needStatusIcon">
...
</span>
<!-- 后置元素 -->
<div class="el-input-group__append" v-if="$slots.append">
...
</div>
</template>
复制代码
前置后置内容及插槽:基本上都是经过props接收的变量或者插槽控制样式及位置偏移,这里我就先“...”了vue
首先会看到input上绑定了这三个事件(在下孤陋寡闻没有见过),因而尝试一下触发时机java
查阅资料后发现,这三个事件不只包括中文输入法还包括语音识别。vuex
下面是MDN上的解释element-ui
相似于
keydown
事件,可是该事件仅在若干可见字符的输入以前,而这些可见字符的输入可能须要一连串的键盘操做、语音识别或者点击输入法的备选词api
由于input组件经常跟form表单一块儿出现,须要作表单验证数组
为了解决中文输入法输入内容时还没将中文插入到输入框就验证的问题浏览器
咱们但愿中文输入完成之后才验证服务器
特指本渣눈.눈
<!-- 文本域结构 -->
<textarea v-else :tabindex="tabindex" class="el-textarea__inner" :value="currentValue" @compositionstart="handleComposition" @compositionupdate="handleComposition" @compositionend="handleComposition" @input="handleInput" ref="textarea" v-bind="$attrs" :disabled="inputDisabled" :readonly="readonly" :style="textareaStyle" @focus="handleFocus" @blur="handleBlur" @change="handleChange" :aria-label="label" >
</textarea>
复制代码
绑定的事件及属性与input差很少,区别是textarea动态控制高度的style
props
computed: {
textareaStyle() {
// merge 从src/utils/merge.js引入 合并对象的方法
return merge({}, this.textareaCalcStyle, { resize: this.resize });
},
},
methods: {
resizeTextarea() {
// 是否运行于服务器 (服务器渲染)
if (this.$isServer) return;
const { autosize, type } = this;
if (type !== 'textarea') return;
if (!autosize) {
this.textareaCalcStyle = {
minHeight: calcTextareaHeight(this.$refs.textarea).minHeight
};
return;
}
const minRows = autosize.minRows;
const maxRows = autosize.maxRows;
this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
}
}
复制代码
calcTextareaHeight 是calcTextareaHeight.js里的方法,计算文本域高度及设置样式
我就直接贴代码和分析的注释了
let hiddenTextarea;
// 预设的一些样式
const HIDDEN_STYLE = ` height:0 !important; visibility:hidden !important; overflow:hidden !important; position:absolute !important; z-index:-1000 !important; top:0 !important; right:0 !important `;
// 预计要用的一些样式属性
const CONTEXT_STYLE = [
'letter-spacing',
'line-height',
'padding-top',
'padding-bottom',
'font-family',
'font-weight',
'font-size',
'text-rendering',
'text-transform',
'width',
'text-indent',
'padding-left',
'padding-right',
'border-width',
'box-sizing'
];
// 获取到一些须要用到的样式
function calculateNodeStyling(targetElement) {
// 获取最终做用到元素的全部样式(返回CSSStyleDeclaration对象)
const style = window.getComputedStyle(targetElement);
// getPropertyValue为CSSStyleDeclaration原型上的方法获取到具体的样式
const boxSizing = style.getPropertyValue('box-sizing');
// 上下内边距
const paddingSize = (
parseFloat(style.getPropertyValue('padding-bottom')) +
parseFloat(style.getPropertyValue('padding-top'))
);
// 上下边框宽度
const borderSize = (
parseFloat(style.getPropertyValue('border-bottom-width')) +
parseFloat(style.getPropertyValue('border-top-width'))
);
// 取出预计要用的属性名和值,以分号拼接成字符串
const contextStyle = CONTEXT_STYLE
.map(name => `${name}:${style.getPropertyValue(name)}`)
.join(';');
// 返回预设要用的样式字符串,上下内边距和, 边框和, boxSizing属性值
return { contextStyle, paddingSize, borderSize, boxSizing };
}
export default function calcTextareaHeight( targetElement, minRows = 1, maxRows = null ) {
// hiddenTextarea不存在则建立textarea元素append到body中
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}
// 取出如下属性值
let {
paddingSize,
borderSize,
boxSizing,
contextStyle
} = calculateNodeStyling(targetElement);
// 给建立的hiddenTextarea添加行内样式并赋值value或palceholder,无则''
hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);
hiddenTextarea.value = targetElement.value || targetElement.placeholder || '';
// 获取元素自身高度
let height = hiddenTextarea.scrollHeight;
const result = {};
// boxSizing不一样 高度计算不一样
if (boxSizing === 'border-box') {
// border-box:高度 = 元素自身高度 + 上下边框宽度和
height = height + borderSize;
} else if (boxSizing === 'content-box') {
// content-box: 高度 = 高度 - 上下内边距和
height = height - paddingSize;
}
hiddenTextarea.value = '';
// 单行文字的高度
let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;
// minRows最小行存在
if (minRows !== null) {
// 最小高度 = 单行高度 * 行数
let minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
// border-box则加上内边距及边框
minHeight = minHeight + paddingSize + borderSize;
}
// minHeight与height取最大值给height赋值
height = Math.max(minHeight, height);
result.minHeight = `${ minHeight }px`;
}
// 最大行存在
if (maxRows !== null) {
// 逻辑同上
let maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
// maxHeight与height取最小值给height赋值
height = Math.min(maxHeight, height);
}
result.height = `${ height }px`;
// 计算完成后移除hiddenTextarea元素
hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
hiddenTextarea = null;
// 暴露包含minHeight及height的对象
return result;
};
复制代码
// 接收form组件注入的属性
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
}
复制代码
size(input的大小)
this.elFormItem.validateState: 与表单验证关联 ,控制表单验证时icon的样式(红x之类的)
computed: {
// 表单验证相关
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];
}
}
复制代码
handleBlur(event) {
this.focused = false;
// 暴露blur事件
this.$emit('blur', event);
if (this.validateEvent) {
// 向上找到ElFormItem组件发布el.form.blur事件并传值
this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);
}
},
setCurrentValue(value) {
// 还在输入而且内容与以前内容相同 return
if (this.isOnComposition && value === this.valueBeforeComposition) return;
// input内容赋值
this.currentValue = value;
// 还在输入return
if (this.isOnComposition) return;
this.$nextTick(_ => {
this.resizeTextarea();
});
// 除了时间选择器其余组件中使用默认为true
if (this.validateEvent) {
// mixin中的方法 意思是向上找到ElFormItem组件发布el.form.change事件并传递当前input内容
this.dispatch('ElFormItem', 'el.form.change', [value]);
}
}
复制代码
路径: src/mixins/emitter.js
// 接收组件名,事件名,参数
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
// 寻找父级,若是父级不是符合的组件名,则循环向上查找
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
// 找到符合组件名称的父级后,发布传入事件。
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
}
复制代码
迭代api友好提示 方便因为用了移除的api报错 找出问题在哪 参见methos中getMigratingConfig事件及**src/mixins/migrating.js **
// 判断韩文的方法(不清楚为何)
import { isKorean } from 'element-ui/src/utils/shared';
methods: {
// 中文或语音输入开始 中 后 触发详见↑
handleComposition(event) {
// 完成输入时
if (event.type === 'compositionend') {
// 输入中标识为false
this.isOnComposition = false;
// 中文或语音输入前的值赋值给当前
this.currentValue = this.valueBeforeComposition;
// 清空以前的值
this.valueBeforeComposition = null;
// 赋值而且向父组件暴露input方法
this.handleInput(event);
// 未完成时
} else {
const text = event.target.value;
const lastCharacter = text[text.length - 1] || '';
// 最后一个字符不是韩文就是在输入中(不是很理解为何要判断最后一个字符是不是韩语)
this.isOnComposition = !isKorean(lastCharacter);
// 输入开始前
if (this.isOnComposition && event.type === 'compositionstart') {
this.valueBeforeComposition = text;
}
}
}
}
复制代码