所谓布局容器,就是页面的总体结构,通常来讲就是<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,则上述布局无效