繼研究了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…