Element分析(組件篇)——Input

input組件相對來說復雜一點,我們先從它用到的一個工具庫calcTextareaHeight.js進行分析。


calcTextareaHeight.js

calcTextareaHeight.js使用來計算文本框的高度的,我們根據代碼順序從上往下進行分析。

HIDDEN_STYLE

HIDDEN_STYLE是一個常量,存儲隱藏時候的css樣式的。

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
`;

CONTEXT_STYLE

CONTEXT_STYLE也是一個常量,用來存儲要查詢的樣式名。

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'
];

calculateNodeStyling

calculateNodeStyling用來獲取結點的某些樣式。

function calculateNodeStyling(node) {
  const style = window.getComputedStyle(node);  // 獲取結點的計算后的樣式,即實際渲染的樣式

  const boxSizing = style.getPropertyValue('box-sizing');  // 獲取 box-sizing 的值

  // 上下的 padding 之和
  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(';');

  return { contextStyle, paddingSize, borderSize, boxSizing };
}

calcTextareaHeight

calcTextareaHeight是最終暴露出去的函數,用來計算文本域的高度。

export default function calcTextareaHeight(
  targetNode,  // 要計算的結點
  minRows = null,  // 最小的行數
  maxRows = null  // 最大的行數
) {
  if (!hiddenTextarea) {  // 來創建一個隱藏的文本域,所有的計算都是在這上面進行的
    hiddenTextarea = document.createElement('textarea');
    document.body.appendChild(hiddenTextarea);
  }

  // 獲取結點一些樣式值
  let {
    paddingSize,
    borderSize,
    boxSizing,
    contextStyle
  } = calculateNodeStyling(targetNode);

  // 設置相應的樣式
  hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);
  // 設置內容,按優先級一次是 結點的 value, 結點的 placeholder, 以及空字符串
  hiddenTextarea.value = targetNode.value || targetNode.placeholder || '';

  // 獲取滾動高度
  let height = hiddenTextarea.scrollHeight;

  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;

  if (minRows !== null) {  // 如果參數傳遞了 minRows
    let minHeight = singleRowHeight * minRows; // 說明最少應當有這么多行的高度
    if (boxSizing === 'border-box') {  // 如果是 border-box,還得加上上下內邊距和上下邊框的寬度
      minHeight = minHeight + paddingSize + borderSize;
    }
    height = Math.max(minHeight, height);  // 取二者最大值
  }
  if (maxRows !== null) {  // 如果參數傳遞了 maxRows
    let maxHeight = singleRowHeight * maxRows;  // 說明最多只能有這么多行的高度
    if (boxSizing === 'border-box') {  // 如果是 border-box,還得加上上下內邊距和上下邊框的寬度
      maxHeight = maxHeight + paddingSize + borderSize;
    }
    height = Math.min(maxHeight, height);  // 取二者最小值
  }

  // 返回文本域應當設置的高度
  return { height: height + 'px'};
};

input.vue

input組件較為繁瑣,我們一點點分析。

生命周期

created

創建的時候會監聽inputSelect事件,并調用inputSelect方法。

created() {
  this.$on('inputSelect', this.inputSelect);
},

inputSelect方法會調用refs上的input的原生的select方法,來選中該input

methods: {
  inputSelect() {
    this.$refs.input.select();
  },
}

mounted

掛載的時候,會調用resizeTextarea方法來設置文本域的大小。

mounted() {
  this.resizeTextarea();
}
methods: {
  resizeTextarea() {
    if (this.$isServer) return;  // 如果是服務端渲染,直接返回,不進行下面的邏輯
    var { autosize, type } = this;
    if (!autosize || type !== 'textarea') return;  // 如果 autosize 是 false,或者當前不是文本域,也直接返回
    const minRows = autosize.minRows;  // 最少行數
    const maxRows = autosize.maxRows;  // 最大行數

    this.textareaStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);  // 計算文本域的高度,并賦值
  },
}

最外層

最外層是一個div,上面設置了一些動態的class

<div :class="[
  type === 'textarea' ? 'el-textarea' : 'el-input',
  size ? 'el-input--' + size : '',
  {
    'is-disabled': disabled,
    'el-input-group': $slots.prepend || $slots.append,
    'el-input-group--append': $slots.append,
    'el-input-group--prepend': $slots.prepend
  }
]">
</div>

type

type是一個prop,它默認設置為text,如果設置為textarea,表明當前是一個文本域。

props: {
  type: {
    type: String,
    default: 'text'
  },
}

size

size也是一個prop,用來設置輸入框的大小,在textarea下無效。

props: {
  size: String,
}

disabled

disabled也是一個prop,用來設置是否可用。

props: {
  disabled: Boolean,
}

prepend、append

這兩個都是在設置輸入框組的時候使用的,通過具名slot傳入,分別放置于input的首和尾。

input

然后,根據type的不同使用v-if分別渲染input或者textarea,我們先分析input部分。

前置元素

前置元素直接通過具名slot傳入。

<div class="el-input-group__prepend" v-if="$slots.prepend">
  <slot name="prepend"></slot>
</div>

input 圖標

圖標也是通過具名slot傳入的,也可以通過prop中的icon傳入圖標名。

<slot name="icon">
  <i
    class="el-input__icon"
    :class="'el-icon-' + icon"
    v-if="icon"
    @click="handleIconClick">
  </i>
</slot>

上面還綁定了一個handleIconClick的點擊事件,它會觸發click事件:

methods: {
  handleIconClick(event) {
    this.$emit('click', event);
  },
}

input

然后是最重要的input部分,上面大部分是prop,不進行講解,其余的我們將一一講解。

<input
  v-if="type !== 'textarea'"
  class="el-input__inner"
  :type="type"  // 類型
  :name="name"  // 名字
  :placeholder="placeholder"  // 默認值
  :disabled="disabled"  // 是否禁用
  :readonly="readonly"  // 是否只讀
  :maxlength="maxlength"  // 輸入的最大長度
  :minlength="minlength"  // 輸入的最小長度(暫時不支持)
  :autocomplete="autoComplete"  // 自動補全
  :autofocus="autofocus"  // 自動聚焦
  :min="min"  // 允許輸入的最小值(數字或者日期)
  :max="max"  // 允許輸入的最大值(數字或者日期)
  :form="form"  // 綁定的表單(不是原生的)
  :value="currentValue"  // 輸入值
  ref="input"  // 引用
  @input="handleInput"  // 輸入事件
  @focus="handleFocus"  // 獲得焦點事件
  @blur="handleBlur"  // 失去焦點事件
>

value

value改變的時候會調用setCurrentValue

watch: {
  'value'(val, oldValue) {
    this.setCurrentValue(val);
  }
},

setCurrentValue是用來改變當前值的。

methods: {
  setCurrentValue(value) {
    if (value === this.currentValue) return;  // 如果新舊值一致直接返回

    this.$nextTick(_ => {
      this.resizeTextarea();  // 下一個DOM更新周期時,重新設置文本域大小
    });

    this.currentValue = value;  // 改變當前值
    this.$emit('input', value);  // 觸發 input 事件
    this.$emit('change', value);  // 觸發 change 事件
    this.dispatch('ElFormItem', 'el.form.change', [value]);  // 向父級的派發 el.form.change 事件
  }
}

handleInput

處理輸入事件。

methods: {
  handleInput(event) {
    this.setCurrentValue(event.target.value);  // 改變當前值
  },
}

handleFocus

handleFocus用來處理獲得焦點的事件,會直接觸發focus事件。

methods: {
  handleFocus(event) {
    this.$emit('focus', event);
  },
}

handleBlur

handleBlur用來處理失去焦點的事件。

methods: {
  handleBlur(event) {
    this.$emit('blur', event);  // 觸發 blur 事件
    this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);  // 向父組件派發 el.form.blur 事件
  },
}

loading

loading會根據計算屬性validating來決定是否渲染。

computed: {
  validating() {
    return this.$parent.validateState === 'validating';
  }
},
<i class="el-input__icon el-icon-loading" v-if="validating"></i>

后置元素

后置元素只能根據具名slot傳入。

<div class="el-input-group__append" v-if="$slots.append">
  <slot name="append"></slot>
</div>

Textarea

如果type設置為textarea則會渲染textarea,上面綁定的都和input類似,不再多說,多了一個textareaStyle,是根據calcTextareaHeight計算出來的。

<textarea
  v-else
  class="el-textarea__inner"
  :value="currentValue"
  @input="handleInput"
  ref="textarea"
  :name="name"
  :placeholder="placeholder"
  :disabled="disabled"
  :style="textareaStyle"
  :readonly="readonly"
  :rows="rows"
  :form="form"
  :autofocus="autofocus"
  :maxlength="maxlength"
  :minlength="minlength"
  @focus="handleFocus"
  @blur="handleBlur">
</textarea>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,969評論 19 139
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內容,還有我對于 Vue 1.0 印象不深的內容。關于...
    云之外閱讀 5,080評論 0 29
  • 下載安裝搭建環境 可以選npm安裝,或者簡單下載一個開發版的vue.js文件 瀏覽器打開加載有vue的文檔時,控制...
    冥冥2017閱讀 6,087評論 0 42
  • 什么是組件 組件(Component)是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用...
    angelwgh閱讀 790評論 0 0
  • 此文基于官方文檔,里面部分例子有改動,加上了一些自己的理解 什么是組件? 組件(Component)是 Vue.j...
    陸志均閱讀 3,866評論 5 14