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

簡介

所謂佈局容器,就是頁面的總體結構,通常來講就是<header>,<footer>,<aside>,<main>等構成的頁面整體架構,官網的示例圖以下css

Element的處理是最外層有一個 container容器,裏面纔是上面4個子組件且只能是上面4個之一,下面依次分析每一個組件的源碼

Container組件

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

<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這個類node

@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屬性,若是有則直接返回水平仍是垂直方向佈局,若是沒有則經過判斷子元素來肯定佈局方向,重點就是這裏的代碼了git

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()同樣是實例,所以有如下屬性github

因此 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中的虛擬節點,當組件被編譯時,每一個<...>就會生成一個虛擬的節點,大體結構以下圖數組

、 擁有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證實了上面的說法瀏覽器

所以這裏給咱們一個怎麼判斷子元素類型的一個啓發方法,能夠借鑑。綜上,這個計算屬性的函數就可以經過子元素來判斷自身flex的佈局方向了bash

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代碼dom

@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,也就是全部元素等比例收縮,對於下圖這種形式的佈局

其中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,則上述佈局無效

相關文章
相關標籤/搜索