簡介
所謂佈局容器,就是頁面的整體結構,一般來說就是<header>,<footer>,<aside>,<main>
等構成的頁面總體架構,官網的示例圖如下
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()一樣是例項,因此有以下屬性
所以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中的虛擬節點,當元件被編譯時,每個<...>
就會生成一個虛擬的節點,大致結構如下圖
、
擁有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的佈局方向了
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,也就是所有元素等比例收縮,對於下圖這種形式的佈局
其中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,則上述佈局無效