继研究了Button组件以后,我又看了一下Link组件的源码,跟Button组件相似,复杂度不是很高。随后挑选了Input组件做为今天的研究对象。git
新建InputShownPage页面,写测试代码:算法
<template>
<div>
<div class="row">
<el-input v-model="input" placeholder="请输入内容"></el-input>
</div>
</div>
</template>
<script>
import ElInput from '../../components/Input/index'
export default {
name: 'InputShownPage',
methods: {
},
components: {
ElInput
}
}
</script>
复制代码
在components文件夹下新建Input组件,接下来咱们实现一个最基础的Input框。element-ui
在Input组件中,写bash
<template>
<div class="el-input">
<template>
<input
class="el-input__inner"
v-bind="$attrs"
/>
</template>
</div>
</template>
<script>
export default {
name: 'ElInput',
};
</script>
复制代码
知识点:$attrs包含了父做用域中不做为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)app
Input默认会撑满整行,因此咱们在测试页InputShownPage为其添加宽度样式:测试
.row .el-input {
width: 180px;
}
复制代码
效果以下图:ui
咱们知道v-model由名为value 的 prop 和名为 input 的事件组成。即this
<el-input :value="input" @input="(value) => { input = value }"></el-input>
复制代码
因为emit('input', $event.target.value)"便可。spa
<input
class="el-input__inner"
v-bind="$attrs"
@input="$emit('input', $event.target.value)"
/>
复制代码
Element的文档上Input的事件有blur,focus,change,input,clear。input已经支持,blur,focus,change是原生input标签就支持的。直接暴露出去便可3d
<input
class="el-input__inner"
v-bind="$attrs"
@input="$emit('input', $event.target.value)"
@focus="$emit('focus', $event.target.value)"
@blur="$emit('blur', $event.target.value)"
@change="$emit('change', $event.target.value)"
/>
复制代码
clear是当Input支持clearable属性的时候的点击事件,那咱们先让Input支持clearable属性。
这样一个基本的Input组件就完成了。
先写测试代码
<el-input
placeholder="请输入内容"
v-model="input"
:disabled="true">
</el-input>
复制代码
再写组件:
:class="[ 'el-input', { 'is-disabled': disabled, } ]"
复制代码
3,给input标签添加:disabled="disabled"
效果以下:
老规矩,先把测试代码写在测试页里。
<el-input placeholder="请输入密码" v-model="input" show-password></el-input>
复制代码
编写Input组件:
增长一个clearable属性,为true的时候开启可清空功能
增长一个清空按钮,在mouseover和focus时候且输入框有值的时候显示,不然隐藏。
给清空按钮添加click相应方法,点击后清空输入框的值。具体代码以下:
<template>
<div :class="[ 'el-input', { 'is-disabled': disabled, 'el-input--suffix': clearable, } ]"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<template>
<input
class="el-input__inner"
v-bind="$attrs"
:value="value"
:disabled="disabled"
@input="$emit('input', $event.target.value)"
@focus="handleFocus"
@blur="handleBlur"
@change="$emit('change', $event.target.value)"
/>
<!-- 后置内容 -->
<span
class="el-input__suffix"
v-if="getSuffixVisible()">
<span class="el-input__suffix-inner">
<i v-if="showClear"
class="el-input__icon el-icon-circle-close el-input__clear"
@mousedown.prevent
@click="clear"
></i>
</span>
</span>
</template>
</div>
</template>
<script>
export default {
name: 'ElInput',
props: {
value: [String, Number],
disabled: Boolean,
clearable: {
type: Boolean,
default: false
},
},
data() {
return {
hovering: false,
focused: false,
};
},
computed: {
nativeInputValue() {
return this.value === null || this.value === undefined ? '' : String(this.value);
},
showClear() {
return this.clearable &&
!this.disabled &&
(this.focused || this.hovering) &&
!!this.nativeInputValue;
},
},
methods: {
handleFocus(event) {
this.focused = true;
this.$emit('focus', event);
},
handleBlur(event) {
this.focused = false;
this.$emit('blur', event);
},
clear() {
this.$emit('input', '');
this.$emit('change', '');
this.$emit('clear');
},
getSuffixVisible() {
return this.showClear
}
},
mounted() {
},
};
</script>
复制代码
效果以下:
老规矩,先把测试代码写在测试页里。
<el-input placeholder="请输入密码" v-model="input" show-password></el-input>
复制代码
再写组件代码:
Element的密码框,比原生的密码框多了一个查看密码的功能,因此实现起来多了个逻辑,默认状况input的type是passport,点击查看按钮,type就变成text。
:type="showPassword ? (passwordVisible ? 'text': 'password') : type"
复制代码
// 为input加入ref
<input ref="input" ... />
// 在clear按钮同级加入查看密码按钮
<i v-if="showPwdVisible"
class="el-input__icon el-icon-view el-input__clear"
@click="handlePasswordVisible"
></i>
handlePasswordVisible() {
this.passwordVisible = !this.passwordVisible;
this.focus();
},
focus() {
this.getInput().focus();
setTimeout(() => {
this.getInput().setSelectionRange(-1,-1);
});
},
blur() {
this.getInput().blur();
},
getInput() {
return this.$refs.input;
},
复制代码
为此咱们还实现了focus和blur这两个方法。setSelectionRange的做用是获取焦点后,可让光标保持在文本后。
这里分为前置icon和后置icon。刚才的可清除和查看密码两个按钮,样式上跟后置icon的效果同样。
这里就不贴测试代码了,跟官网上的同样,分为属性和slot两种方式。
编写组件代码:
<!-- 前置内容 -->
<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>
...
<template v-if="!showClear || !showPwdVisible">
<slot name="suffix"></slot>
<i class="el-input__icon"
v-if="suffixIcon"
:class="suffixIcon">
</i>
</template>
复制代码
'el-input--prefix': $slots.prefix || prefixIcon,
'el-input--suffix': $slots.suffix || suffixIcon || clearable || showPassword
复制代码
效果以下:
显然,文本域的type再也不是text,是textarea。因为textarea没有先后置内容和元素。且有独有resize、自适应高度特性。因此在编写的时候,用一个新标签作它的呈现。
<template v-if="type !== 'textarea'">
// input标签的内容
</template>
<textarea
v-else
ref="textarea"
class="el-textarea__inner"
v-bind="$attrs"
:disabled="disabled"
:style="textareaStyle"
@input="$emit('input', $event.target.value)"
@focus="handleFocus"
@blur="handleBlur"
@change="$emit('change', $event.target.value)"
>
</textarea>
复制代码
效果以下:
思路:要实现自适应高度,第一步就是watch文本value,文本变化时,触发计算文本框的高度,第二步是根据输入的参数autosize="{ minRows: 2, maxRows: 4}实现计算高度算法。
// 第一步
watch: {
value() {
this.$nextTick(this.resizeTextarea);
},
type() { // type改变的时候,也须要计算下高度
this.$nextTick(this.resizeTextarea);
}
},
// 第二步
resizeTextarea() {
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不贴了,可在我文档底部上传的码云查看,也能够直接差Element源码,直接搬过来的。
写出测试代码
<el-input placeholder="请输入内容" v-model="input">
<template slot="prepend">Http://</template>
<template slot="append">.com</template>
</el-input>
复制代码
在组件中添加对应标签和样式便可。
// 给根元素添加样式
'el-input-group': $slots.prepend || $slots.append,
'el-input-group--append': $slots.append,
'el-input-group--prepend': $slots.prepend,
<!-- 前置元素 -->
<div class="el-input-group__prepend" v-if="$slots.prepend">
<slot name="prepend"></slot>
</div>
<!-- 后置元素 -->
<div class="el-input-group__append" v-if="$slots.append">
<slot name="append"></slot>
</div>
复制代码
效果以下:
经过控制样式便可,给跟标签添加样式:
this.size ? 'el-input--' + this.size : '',
复制代码
带输入建议 自定义模板 远程搜索 这三点都是另外一个Input组件el-autocomplete的特性。暂不写在这里。先研究输入长度限制特性,Element的Input组件会有显示总字数和已写字数。
// 对于text
<span v-if="isWordLimitVisible" class="el-input__count">
<span class="el-input__count-inner">
{{ textLength }}/{{ upperLimit }}
</span>
</span>
// 对于textarea
<span v-if="isWordLimitVisible && type === 'textarea'" class="el-input__count">{{ textLength }}/{{ upperLimit }}</span>
// 若是showWordLimit为ture,显示总字数和已写字数
isWordLimitVisible() {
return this.showWordLimit &&
this.$attrs.maxlength &&
(this.type === 'text' || this.type === 'textarea') &&
!this.disabled &&
!this.showPassword;
},
// 总字数
upperLimit() {
return this.$attrs.maxlength;
},
// 已写字数
textLength() {
if (typeof this.value === 'number') {
return String(this.value).length;
}
return (this.value || '').length;
},
复制代码
效果以下:
至此,Element的Input组件大部分功能咱们都模仿完毕了。
本文的全部代码已上传至码云:gitee.com/DaBuChen/my…