Element原始碼分析系列2-Container(佈局容器)

超級索尼發表於2019-03-04

簡介

所謂佈局容器,就是頁面的整體結構,一般來說就是<header>,<footer>,<aside>,<main>等構成的頁面總體架構,官網的示例圖如下

Element原始碼分析系列2-Container(佈局容器)
Element的處理是最外層有一個container容器,裡面才是上面4個子元件且只能是上面4個之一,下面依次分析每個元件的原始碼

Container元件

顧名思義,它是一個容器元件,承載子元件,容器元件應該有什麼樣的特性,無非就是塊狀,高度自適應等,Element加了一條規則:當子元素中包含 <el-header><el-footer> 時,全部子元素會垂直上下排列,否則會水平左右排列,這是不是很神奇,下面上程式碼來探究一下原理,程式碼點此

<template>
  <section class="el-container" :class="{ 'is-vertical': isVertical }">
    <slot></slot>
  </section>
</template>

<script>
  export default {
    name: 'ElContainer',
    //自定義屬性,其他元件會用到
    componentName: 'ElContainer',
    props: {
      direction: String
    },
    computed: {
      isVertical() {
        if (this.direction === 'vertical') {
          return true;
        } else if (this.direction === 'horizontal') {
          return false;
        }
        return this.$slots && this.$slots.default
          ? this.$slots.default.some(vnode => {
            const tag = vnode.componentOptions && vnode.componentOptions.tag;
            return tag === 'el-header' || tag === 'el-footer';
          })
          : false;
      }
    }
  };
</script>
複製程式碼

這就是一個典型的單檔案vue元件形式,只不過樣式部分被分離出去了,首先來看template部分,發現其實就是<section>的封裝而已,後面的元件也都採用了利於SEO的語義化tag標籤而不是div,其實它們本質上就是塊狀元素,功能和div是一樣的,注意<section>裡面的<slot>,這就是承載子元件的插槽,如果不寫則將會沒有任何子元素,且是一個匿名插槽,接下來我們來看樣式,el-container這個類

@import "mixins/mixins";

@include b(container) {
  display: flex;
  flex-direction: row;
  flex: 1;
  flex-basis: auto;
  box-sizing: border-box;
  min-width: 0;

  @include when(vertical) {
    flex-direction: column;
  }
}
複製程式碼

可以看出<section>其實是flex佈局,且預設方向為橫向,其中flex-basis:auto設定其基準長度為自適應,這裡的flex:1表示如果container被父container包圍,那麼它會分配剩餘的寬或高

然後我們來看js部分,isVertical是一個計算屬性,首先判斷是否有direction屬性,如果有則直接返回水平還是垂直方向佈局,如果沒有則通過判斷子元素來確定佈局方向,重點就是這裡的程式碼了

isVertical() {
    ...
    return this.$slots && this.$slots.default
      ? this.$slots.default.some(vnode => {
        const tag = vnode.componentOptions && vnode.componentOptions.tag;
        return tag === 'el-header' || tag === 'el-footer';
      })
      : false;
}
複製程式碼

它是一個3元運算子,首先判斷this.$slots&& this.$slots.default,如果不存在直接返回false,不存在的情況就是子元素為空。this.$slots是元件的例項屬性,元件是可複用的Vue的例項,和 new Vue()一樣是例項,因此有以下屬性

Element原始碼分析系列2-Container(佈局容器)
所以this.$slots.default返回了所有沒有被包含在具名插槽中的子元素,如果這些子元素存在,那麼開始依次遍歷這些子元素,注意some高階函式的使用,這裡就是要在所有子元素中檢視是否存在<el-header>或者<el-footer>,some所遍歷的陣列只要有一個為true,則整體返回true,否則為false,而every則是要所有都是true才返回true,這裡的高階函式簡化了程式碼量,如果用一般的for迴圈顯得不那麼優雅

接下來是如何判斷子元素是<el-header>或者<el-footer>,這裡首先要明確this.$slots.default是個陣列,裡面的每個元素都是一個VNodeVNode是虛擬dom中的虛擬節點,當元件被編譯時,每個<...>就會生成一個虛擬的節點,大致結構如下圖

Element原始碼分析系列2-Container(佈局容器)
、 擁有tag,childern,text等屬性,而componentOptions裡面才包含元素的tag名稱,注意VNode的tag不是我們想要的,進入Vue原始碼查詢相關程式碼

const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
複製程式碼

發現第一個引數就是tag,其實是有字首的,而componentOptions{ Ctor, propsData, listeners, tag, children }這麼個物件,代表元件選項屬性,這裡面的tag才是我們想要的,列印出VNode證明了上面的說法

Element原始碼分析系列2-Container(佈局容器)

因此這裡給我們一個怎麼判斷子元素型別的一個啟發方法,可以借鑑。綜上,這個計算屬性的函式就能夠通過子元素來判斷自身flex的佈局方向了

Footer元件

由於剩下的元件都很相似,這裡就分析一個Footer,程式碼如下

<template>
  <footer class="el-footer" :style="{ height }">
    <slot></slot>
  </footer>
</template>

<script>
  export default {
    name: 'ElFooter',
    //自定義屬性,便於其他元件獲取該元件的名稱
    componentName: 'ElFooter',
    props: {
      height: {
        type: String,
        default: '60px'
      }
    }
  };
</script>
複製程式碼

由此可見仍然是封裝了原生的<footer>,並有一個預設高度,footer本質上就是一個塊狀元素而已,下面來檢視scss程式碼

@import "mixins/mixins";
@import "common/var";

@include b(footer) {
  padding: $--footer-padding;
  box-sizing: border-box;
  flex-shrink: 0;
}
複製程式碼

注意flex-shrink:0這個程式碼,這表示當父容器寬度不夠時,footer不會收縮而是保持原本的寬度,也就是說footer是永遠不會被壓縮,就算超出容器。flex-shrink預設為1,也就是所有元素等比例收縮,對於下圖這種形式的佈局

Element原始碼分析系列2-Container(佈局容器)
其中header和footer的flex-shrink都是0,也就是不會壓縮,而main中的程式碼flex:1表示了只有main會被壓縮或者擴張,flex:1是flex:grow:1,flex:shrink:1,flex-basis:auto的簡寫,又因為flex:grow預設值為0,所以只有main會被壓縮或擴張,header和footer都不變

總結

綜上,整個佈局其實就是對原生的封裝,主要就是container的處理,如果瀏覽器不支援flex,則上述佈局無效

相關文章