elementUI源码分析-02-layout布局

一、layout布局组件的引用方式

基础的引用方式如下

<el-row>
  <el-col :span="24"><div class="grid-content bg-purple-dark"></div></el-col>
</el-row>
<el-row>
  <el-col :span="12"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="12"><div class="grid-content bg-purple-light"></div></el-col>
</el-row>

二、功能点逐个击破

官网介绍的功能点如下

  • ele的布局组件是使用基础的24分栏,迅速简便的创建布局。并通过 col 组件的 span 属性我们就可以自由地组合布局。
  • Row 组件 提供 gutter 属性来指定每一栏之间的间隔,默认间隔为 0。
  • 通过制定 col 组件的 offset 属性可以指定分栏偏移的栏数。
  • 通过 flex 布局来对分栏进行灵活的对齐
  • 参照了 Bootstrap 的 响应式设计,预设了五个响应尺寸:xs、sm、md、lg 和 xl,实现响应式布局。

我们可以根据功能点对组件进行分析

el-row的组件的源码:

export default {
    name: 'ElRow', // 组件的name属性

    componentName: 'ElRow',
        // 接受props属性
    props: {
        // 通过设置tag,自定义渲染的元素标签
        tag: { 
            type: String,
            default: 'div'
        },
        // 栅格间隔
        gutter: Number,
        // 布局模式,可选择flex
        type: String,
        // flex 布局下的水平排列方式
        justify: {
            type: String,
            default: 'start'
        },
        // flex 布局下的垂直排列方式
        align: {
            type: String,
            default: 'top'
        }
    },

    computed: {
        // 如果设置了gutter,那么将其转化为margin对应值
        // 比如设置了gutte=20,那么实际上是给当前元素设置了padding-left: 10px;padding-right: 10px;
        style() {
            const ret = {};

            if (this.gutter) {
                ret.marginLeft = `-${this.gutter / 2}px`;
                ret.marginRight = ret.marginLeft;
            }

            return ret;
        }
    },

    render(h) {
        return h(this.tag, {
            class: [
                'el-row',
                this.justify !== 'start' ? `is-justify-${this.justify}` : '',
                this.align !== 'top' ? `is-align-${this.align}` : '',
                { 'el-row--flex': this.type === 'flex' }
            ],
            style: this.style
        }, this.$slots.default);
    }
};

el-col源码:

export default {
    name: 'ElCol',

    props: {
        // 栅格占据的列数
        span: {
            type: Number,
            default: 24
        },
        // 自定义元素标签
        tag: {
            type: String,
            default: 'div'
        },
        // 栅格左侧的间隔格数
        offset: Number,
        // 栅格向左移动格数
        pull: Number,
        // 栅格像右移动格数
        push: Number,
        // <768px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
        xs: [Number, Object],
        // ≥768px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
        sm: [Number, Object],
        // ≥992px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
        md: [Number, Object],
        // ≥1200px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
        lg: [Number, Object],
        // ≥1920px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
        xl: [Number, Object]
    },

    computed: {
        gutter() {
            // col逐级往上寻找父节点,判断是否是ElRow, 找到距离最近的父级ElRow
            // 如果找到ElRow ,则返回gutter 属性,如果找不到继续往上直到不存在父节点则返回0
            // 因为ElRow和ElCol可能嵌套了其他组件
            let parent = this.$parent;
            while (parent && parent.$options.componentName !== 'ElRow') {
                parent = parent.$parent;
            }
            return parent ? parent.gutter : 0;
        }
    },
    render(h) {
        let classList = []; // 设置标签的class属性
        let style = {}; // 设置标签的行内css

        if (this.gutter) {
            style.paddingLeft = this.gutter / 2 + 'px';
            style.paddingRight = style.paddingLeft;
        }
        // 如果添加了span, offset, pull, push,那么将添加对应的class类
        ['span', 'offset', 'pull', 'push'].forEach(prop => {
            if (this[prop] || this[prop] === 0) {
                classList.push(
                    prop !== 'span'
                        ? `el-col-${prop}-${this[prop]}`
                        : `el-col-${this[prop]}`
                );
            }
        });
        // 如果添加了xs, sm, md, lg, xl,那么将添加对应的class类
        ['xs', 'sm', 'md', 'lg', 'xl'].forEach(size => {
            if (typeof this[size] === 'number') {
                classList.push(`el-col-${size}-${this[size]}`);
            } else if (typeof this[size] === 'object') {
                let props = this[size];
                Object.keys(props).forEach(prop => {
                    classList.push(
                        prop !== 'span'
                        ? `el-col-${size}-${prop}-${props[prop]}`
                        : `el-col-${size}-${props[prop]}`
                    );
                });
            }
        });

        return h(this.tag, {
            class: ['el-col', classList],
            style
        }, this.$slots.default);
    }
};

两个组件中都出现了render函数,先来看看render函数

render函数

大多数开发过程中,我们都会使用‘单文件组件’,在template中使用html语法组件页面,编译器拿到template模板时将其转义成vnode函数。

但是其实在一些场景中,也可以直接用JavaScript的完全编程能力,这就是渲染函数,即render函数。而用render函数构建DOM时,vue就免去了转译的过程。render函数就可以使用js语言来构建DOM

当使用render函数描述vnode时,vue提供一个函数,这个函数是构建dom所需要的工具,官网起名为createElement,简写成h函数。

render函数接受一个createElement参数,并返回该函数创建的vnode。

createElement接受三个参数

  • 参数1:标签名,必填项。
  • 参数2:该标签attribute 对应的数据对象。可选。
  • 参数3:子虚拟节点

比如组件源码中的render函数

// 渲染函数 
render(h) {
    // 接受createElement函数,并返回该函数创建的vnode
    // 该函数所需三个参数,标签名,属性,子虚拟节点
    return h(this.tag, {
        class: [
            'el-row',
            this.justify !== 'start' ? `is-justify-${this.justify}` : '',
            this.align !== 'top' ? `is-align-${this.align}` : '',
            { 'el-row--flex': this.type === 'flex' }
        ],
        style: this.style
    }, this.$slots.default);
}

下面我们整体梳理下这两个组件具体做了什么,一一解密每一个功能点是如何实现的。

解密布局

ele的布局组件是使用基础的24分栏,迅速简便的创建布局。并通过 col 组件的 span 属性我们就可以自由地组合布局。

<el-row>
  <el-col :span="12"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="12"><div class="grid-content bg-purple-light"></div></el-col>
</el-row>

首先row和col一般情况下都是配合使用的,ele的布局组件是使用基础的24分栏,迅速简便的创建布局。通过 row 和 col 组件,并通过 col 组件的 span 属性我们就可以自由地组合布局。

默认div的宽度是100%独占一行的,让多个el-col在一行,让他们的宽占据一定的百分比,就可以实现分栏的效果。设置百分比,就用过设置不同的css即可实现

假设给span赋值12的时候,那么相当于给元素添加了一个el-col-12的class类,因为总共是24分栏,那么它的宽度就是50%。

.el-col-12 {
    width: 50%
}

因此,通过设置span,可以给元素添加相应的class类名,从而设置对应的宽度,即可实现快速创建布局。

解密间隔

Row 组件 提供 gutter 属性来指定每一栏之间的间隔,默认间隔为 0。

<el-row :gutter="20">
  <el-col :span="16"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
</el-row>

通过给row组件添加gutter值,就可以设置col的间隔,在源码中可以看到,col逐级往上寻找父节点,判断是否是row, 找到距离最近的父级row,如果找到row ,则返回父级的gutter 属性,如果找不到继续往上直到不存在父节点则返回0,因为row和col可能嵌套了其他组件。

获取到了gutter就通过给自身设置padding样式,结合box-sizing:border-box,就可以实现分栏间隔

比如给gutter设置20的时候,实际上是给元素添加padding-left:10px;padding-right:10px;

[class*=el-col-] {
    float: left;
    box-sizing: border-box;
}
{
    padding-left: 10px;
    padding-right: 10px;
}

解密偏移

通过制定 col 组件的 offset 属性可以指定分栏偏移的栏数。

<el-row :gutter="20">
  <el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="6" :offset="6"><div class="grid-content bg-purple"></div></el-col>
</el-row>

在源码中可以看到,给col设置offset属性,相当于给元素设置了el-col-offset-6的class类,这个类给元素设置了对应的margin百分比,即实现了对应的偏移数

.el-col-offset-6 {
    margin-left: 25%;
}

同理,当给col设置pull=6时,实际上是给元素添加了el-col-pull-6类,设置了对应的定位偏移

.el-col-pull-6 {
    position: relative;
    right: 25%;
}

设置push时,是添加了el-col-push-6类

.el-col-push-6 {
    position: relative;
    left: 25%;
}

解密支持flex

通过 flex 布局来对分栏进行灵活的对齐

row可以通过type=flex将其设置为flex布局,然后用过justify设置flex布局下的水平排列方式,通过align设置布局下的垂直排列方式。

根据设置的justify的属性值,设置对应的class、比如justify="center",就设置了class为is-justify-center,从而设置了对应的样式

.el-row--flex.is-justify-center {
    justify-content: center;
}
.el-row--flex {
    display: flex;
}

algin也是同理。

解密响应式

参照了 Bootstrap 的 响应式设计,预设了五个响应尺寸:xs、sm、md、lg 和 xl

xs: <768px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
sm: ≥768px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
md: ≥992px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
lg: ≥1200px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})
xl: ≥1920px 响应式栅格数或者栅格属性对象 eg: number/object (例如: {span: 4, offset: 4})

这个也很简单,比如下面这段响应式布局

<el-row :gutter="10">
  <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="1"><div class="grid-content bg-purple"></div></el-col>
</el-row>

编译出来,其实结合了media标签,设置对应的class

<div class="el-col el-col-24 el-col-xs-8 el-col-sm-6 el-col-md-4 el-col-lg-3 el-col-xl-1"></div>

最后编译出来的部分代码如下

@media only screen and (min-width: 768px){
    ....
}

总结:

其实ele的布局组件layout还是相对来说比较好理解的,就是通过设置对应的属性值,生成对应的class去渲染布局,然后用render函数再去渲染对应的vnode。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 简介:通过基础的 24 分栏,迅速简便地创建布局。标签为el-col和el-row 1、col 源码路径:pack...
    林帅进来了阅读 2,560评论 1 2
  • 目录: vue组件系列课程简介 页面结构介绍 安装vue,vue-cli, 安装vant UI框架 导入组件 各页...
    独绽2018阅读 43,752评论 0 6
  • 布局方式 先聊聊布局方面的知识,先列据一个实现三栏水平布局(左右定宽,中间自适应)案列,目前有圣杯布局、双飞翼布局...
    dosher_多舍阅读 11,418评论 0 7
  • 本文主要讲述页面布局样式方面涉及的知识点,更全面的对CSS相应的技术进行归类、整理、说明,没有特别详细的技术要点说...
    Joel_zh阅读 840评论 0 1
  • Layout 布局 通过基础的 24 分栏,迅速简便地创建布局。基础布局使用单一分栏创建基础的栅格布局。通过 ro...
    辽A丶孙悟空阅读 8,979评论 0 13