今日简单分享 input 组件的实现原理,主要从以下五个方面来分享:
1、input 组件的页面结构
2、input 组件的属性
3、input 组件的 slot
4、input 组件的事件
5、input 组件的方法
一、input 组件的页面结构。
二、input 组件的属性。
2.1 type 属性,类型 string,默认 text。
2.1.1 type 的值一般为 textarea、text 及原生 input 标签 type 值。
2.1.2 type 取值逻辑。
2.2 value / v-model 属性,类型 string,无默认值。
2.3 maxlength 属性,原生属性,最大输入长度,类型 number,无默认值。
2.4 minlength 属性,原生属性,最小输入长度,类型 number,无默认值。
小结:vm.$attrs 的作用,接受父组件传递过来的自定义属性,并将其渲染到 dom 节点上,如下图:
2.5 show-word-limit 属性,是否显示输入字数统计,只在 type = "text"
或 type = "textarea"
时有效,类型 boolean,默认 false。
页面显示效果如下:
2.6 placeholder 属性,输入框占位文本,类型 string,无默认值。
源码当中的 props 属性中并未有 placeholder 属性,此属性是通过 v-bind="$attrs" 的方式来传递到 input 标签上的。
2.7 clearable 属性,是否可清空,类型 boolean,默认 false。
2.8 disabled 属性,禁用,类型 boolean,默认 false。
2.9 size 属性,输入框尺寸,只在 type!="textarea"
时有效,类型 string,medium / small / mini,无默认值。
2.10 prefix-icon 属性,输入头部图标,类型 string,无默认值。
2.11 suffix-icon 属性,输入框尾部图标,类型 string,无默认值。
挂载后置图标时,多了一个计算属性的判断:
2.12 rows 属性,输入框行数,只对 type="textarea"
有效,类型 number,默认 2。
源码当中的 props 中并未传递此属性,此属性是 html 原生的属性,通过 v-bind="$attrs" 挂载到标签上的。
2.13 autosize 属性,自适应内容高度,只对 type="textarea"
有效,可传入对象,如,{ minRows: 2, maxRows: 6 },类型 boolean / object,默认 false。
autoSize 是对 rows 的特殊处理,如下图。
calcTextareaHeight 方法,计算 textarea 的高度,实现在最大行数之前高度的自适应。
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) {
// targetElement: textarea dom
// window.getComputedStyle: 获取元素对 style,返回的是一个对象
const style = window.getComputedStyle(targetElement);
// style.getPropertyValue:从 style 对象中获取 box-sizing 的值
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'));
// 拼装 style 样式
const contextStyle = CONTEXT_STYLE.map(
(name) => `${name}:${style.getPropertyValue(name)}`
).join(';');
return { contextStyle, paddingSize, borderSize, boxSizing };
}
export default function calcTextareaHeight(
targetElement,
minRows = 1,
maxRows = null
) {
if (!hiddenTextarea) {
// 存储创建的隐藏域文本,作用:测量文本高度;应用样式;考虑边框和内边距;支持最小和最大行数;重置和清理
hiddenTextarea = document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}
let {
paddingSize,
borderSize,
boxSizing,
contextStyle,
} = calculateNodeStyling(targetElement);
// 设置 style
hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);
hiddenTextarea.value = targetElement.value || targetElement.placeholder || '';
let height = hiddenTextarea.scrollHeight;
const result = {};
// ie 盒模型的高度计算 高度不包含 padding border
if (boxSizing === 'border-box') {
height = height + borderSize;
} else if (boxSizing === 'content-box') {
// 标准盒模型的高度计算 高度包含 padding border
height = height - paddingSize;
}
hiddenTextarea.value = '';
// 单行文本的高度
let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;
if (minRows !== null) {
let minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
minHeight = minHeight + paddingSize + borderSize;
}
height = Math.max(minHeight, height);
result.minHeight = `${minHeight}px`;
}
if (maxRows !== null) {
let maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
height = Math.min(maxHeight, height);
}
result.height = `${height}px`;
hiddenTextarea.parentNode &&
hiddenTextarea.parentNode.removeChild(hiddenTextarea);
hiddenTextarea = null;
// result { height:'',minHeight:'' }
return result;
}
2.14 autocomplete 原生属性,自动补全,类型 string,默认 off。
2.15 name 属性,原生属性,类型 string,无默认值。
源码 props 中无此属性,通过 v-bind="$attrs" 传递到页面中的。设置 name 的作用,表单项的唯一标识,可读性更高。
2.16 readonly 属性,原生属性,只读,类型 boolean,默认 false。
2.17 max 属性,原生属性,设置最大值,类型 number,无默认值。
2.18 min 属性,原生属性,设置最小值,类型 number,无默认值。
2.19 step 属性,原生属性,设置输入字段的合法数字间隔,类型 number,无默认值。
上面三个属性,在源码的 props 当中都无此属性,使用过 v-bind="$attrs" 的方式进行传递的。
2.20 resize 属性,控制是否能被用户缩放,类型 string,有这些值 none, both, horizontal, vertical,无默认值。
2.21 autofocus 属性,原生属性,自动获取焦点,类型 boolean,默认 false。
在源码的 props 当中都无此属性,使用过 v-bind="$attrs" 的方式进行传递的。
2.22 form 属性,原生属性,类型 string,无默认值。
<form id="myForm" action="/submit_form">
<!-- 其他表单元素 -->
</form>
<input type="text" name="username" form="myForm">
2.23 label 属性,输入框关联的label文字,类型 string,无默认值。
2.24 tabindex 属性,输入框的tabindex,类型 string,无默认值。
tabindex 全局属性指示其元素是否可以聚焦,以及它是否/在何处参与顺序键盘导航(通常使用Tab键,因此得名)。
2.25 validate-event 属性,输入时是否触发表单的校验,类型 boolean,默认 true。
三、input 组件的 slot 挂载。
提供 slot 插槽,运行用户在指定位置挂载用户自定义的内容。
3.1 prefix slot,输入框头部内容,只对 type="text"
有效。
3.2 suffix slot,输入框尾部内容,只对 type="text"
有效。
3.3 prepend slot,输入框前置内容,只对 type="text"
有效。
3.4 append slot,输入框后置内容,只对 type="text"
有效。
四、input 组件的事件。
4.1 blur 事件,在 Input 失去焦点时触发,(event: Event)。
4.2 focus 事件,在 Input 获得焦点时触发,(event: Event)。
4.3 change 事件,仅在输入框失去焦点或用户按下回车时触发,(value: string | number)。
4.4 input 事件,在 Input 值改变时触发,(value: string | number)。
重点解决 isComposing 这个变量,它是通过下面这三个时间来实现的,主要解决在输入拼音时,所触发的事假何时执行的问题。
compositionstart:文本合成系统如 input method editor(即输入法编辑器)开始新的输入合成时会触发 compositionstart
事件。例如,当用户使用拼音输入法开始输入汉字时,这个事件就会被触发。
参考链接:compositionstart - Web API 接口参考 | MDN
compositionupdate:事件触发于字符被输入到一段文字的时候(这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词)。
参考链接:compositionupdate - Web API 接口参考 | MDN
compositionend:当文本段落的组成完成或取消时,compositionend 事件将被触发 (具有特殊字符的触发,需要一系列键和其他输入,如语音识别或移动中的字词建议)。
参考链接:compositionend - Web API 接口参考 | MDN
对应源码部分:
compositionstart 部分:
compositionupdate 部分:
compositionend 部分:
4.4 clear 事件,在点击由 clearable
属性生成的清空按钮时触发。
五、input 组件的方法。父组件可以通过 ref 的形式,调用组件内部的方法。
5.1 focus,使 input 获取焦点。
5.2 focus,使 input 失去焦点。
5.3 select,选中 input 中的文字。
方法使用部分:
展示效果: