所謂佈局容器,就是頁面的總體結構,通常來講就是<header>,<footer>,<aside>,<main>
等構成的頁面整體架構,官網的示例圖以下css
container
容器,裏面纔是上面4個子組件且只能是上面4個之一,下面依次分析每一個組件的源碼
顧名思義,它是一個容器組件,承載子組件,容器組件應該有什麼樣的特性,無非就是塊狀,高度自適應等,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
是個數組,裏面的每一個元素都是一個VNode
,VNode
是虛擬dom中的虛擬節點,當組件被編譯時,每一個<...>
就會生成一個虛擬的節點,大體結構以下圖數組
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
,代碼以下架構
<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,也就是全部元素等比例收縮,對於下圖這種形式的佈局
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,則上述佈局無效