Element 2 組件源碼剖析之布局容器

0x00 簡介

前文分析過組件的 布局柵格化(Grid Layout) ,通過基礎的 24 分欄,迅速簡便地創建布局。

本文將介紹用於布局的容器組件,使用 Flexbox 功能將其所控制區域設定為特定的布局,方便快速搭建頁面的基本結構。本文將深入分析組件源碼,剖析其實現原理,耐心讀完,相信會對您有所幫助。 組件文檔

更多組件剖析詳見 👉 📚 Element 2 源碼剖析組件總覽

0x01 布局容器

布局容器提供5個組件,支援多層嵌套,方便快速搭建頁面的基本結構:

  • <el-container>:布局容器,其下可嵌套 <el-header> <el-footer> <el-aside> <el-main> 或 <el-container> 本身,可以放在任何父容器中。當子元素中包含 <el-header> 或 <el-footer> 時,全部子元素會垂直上下排列,否則會水平左右排列。
  • <el-header>:頂部容器,其下可嵌套任何元素,只能放在 <el-container> 中。
  • <el-aside>:側邊欄容器,其下可嵌套任何元素,只能放在 <el-container> 中。
  • <el-main>:主要區域容器,其下可嵌套任何元素,只能放在 <el-container> 中。
  • <el-footer>:底部容器,其下可嵌套任何元素,只能放在 <el-container> 中。

以上組件採用了 flex 布局,使用前請確定目標瀏覽器是否兼容。此外,<el-container> 的子元素只能是後四者,後四者的父元素也只能是 <el-container>

以下程式碼通過多層嵌套可以實現常用的頁面布局, 更多常用布局實現詳見 官方文檔

<el-container>
  <el-header>Header</el-header>
  <el-container>
    <el-aside width="200px">Aside</el-aside>
    <el-container>
      <el-main>Main</el-main>
      <el-footer>Footer</el-footer>
    </el-container>
  </el-container>
</el-container>

image.png

0x02 程式碼實現

container 布局容器

組件 container 封裝了 <section>元素,包含沒有後備內容(默認值)的匿名插槽 。組件定義了direction的 prop 屬性,用於子元素的排列方向。

// packages\container\src\main.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() {
        // ...
      }
    }
  };
</script>

若沒有定義了direction屬性值,組件通過tag判斷子元素中包含 <el-header> 或 <el-footer> 時,全部子元素會垂直上下排列,否則會水平左右排列。 componentOptions類型定義

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

header 頂部容器

組件 header 封裝了 <header>元素,包含一個 slot 。組件定義了height的 prop 屬性,設置頂部容器高度,默認值60px

// packages\header\src\main.vue
<template>
  <header class="el-header" :style="{ height }">
    <slot></slot>
  </header>
</template>
<script>
  export default {
    name: 'ElHeader',
    componentName: 'ElHeader',
    props: {
      height: {
        type: String,
        default: '60px'
      }
    }
  };
</script>

aside 側邊欄容器

組件 aside 封裝了 <aside>元素,包含一個 slot 。組件定義了width的 prop 屬性,設置側邊欄寬度,默認值300px

// packages\aside\src\main.vue
<template>
  <aside class="el-aside" :style="{ width }">
    <slot></slot>
  </aside>
</template>
<script>
  export default {
    name: 'ElAside',
    componentName: 'ElAside',
    props: {
      width: {
        type: String,
        default: '300px'
      }
    }
  };
</script>

main 主要區域(內容)容器

組件 main 封裝了 <main>元素,包含一個 slot 。

// packages\main\src\main.vue
<template>
  <main class="el-main">
    <slot></slot>
  </main>
</template>
<script>
  export default {
    name: 'ElMain',
    componentName: 'ElMain'
  };
</script>

組件 footer 封裝了 <footer>元素,包含一個 slot。組件定義了height的 prop 屬性,設置頂部容器高度,默認值60px

// packages\footer\src\main.vue
<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>

0x03 組件樣式

組件樣式源碼使用 scss 的混合指令 b、 when 嵌套生成組件樣式。

// packages\theme-chalk\src\common\var.scss
$--header-padding: 0 20px !default;
$--footer-padding: 0 20px !default;
$--main-padding: 20px !default;

// packages\theme-chalk\src\container.scss
@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;
  }
}

// packages\theme-chalk\src\header.scss
@include b(header) {
  padding: $--header-padding;
  box-sizing: border-box;
  flex-shrink: 0;
}

// packages\theme-chalk\src\footer.scss
@include b(footer) {
  padding: $--footer-padding;
  box-sizing: border-box;
  flex-shrink: 0;
} 

// packages\theme-chalk\src\main.scss
@include b(main) {
  // IE11 supports the <main> element partially //caniuse.com/#search=main
  display: block;
  flex: 1;
  flex-basis: auto;
  overflow: auto;
  box-sizing: border-box;
  padding: $--main-padding;
}

// packages\theme-chalk\src\aside.scss
@include b(aside) {
  overflow: auto;
  box-sizing: border-box;
  flex-shrink: 0;
} 

使用 gulpfile.js編譯 scss 文件轉換為CSS,經過瀏覽器兼容、格式壓縮,最後生成樣式內容如下。

/* packages\theme-chalk\lib\container.css */
.el-container {
  display: flex;
  flex-direction: row;
  flex: 1;
  flex-basis: auto;
  box-sizing: border-box;
  min-width: 0;
}

.el-container.is-vertical {
  flex-direction: column;
}

/* packages\theme-chalk\lib\main.css */
.el-main {
  display: block;
  flex: 1;
  flex-basis: auto;
  overflow: auto;
  box-sizing: border-box;
  padding: 20px;
}

/* packages\theme-chalk\lib\aside.css */
.el-aside {
  overflow: auto;
  box-sizing: border-box;
  flex-shrink: 0;
}

/* packages\theme-chalk\lib\header.css */
.el-header {
  padding: 0 20px;
  box-sizing: border-box;
  flex-shrink: 0;
}

/* packages\theme-chalk\lib\footer.css */
.el-footer {
  padding: 0 20px;
  box-sizing: border-box;
  flex-shrink: 0;
}

容器布局實現使用 CSS Flexbox,flex-basisflex-shrinkflex 等屬性的語法內容請閱讀 Flex 布局教程:語法篇 阮一峰

前文曾提到<el-container> 的子元素只能是後四者,後四者的父元素也只能是 <el-container>。因為 只有container 組件指定為 Flex 布局,其餘組件若是想要Flex 布局生效,只能將組件作為 container 的子組件,當然 container 可以自包含。

0x04 關注專欄

此文章已收錄到專欄中 👇,可以直接關注。