elementUI源码分析-02-layout布局

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

基础的引用方式如下


  

二、功能点逐个击破

官网介绍的功能点如下

  • 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 属性我们就可以自由地组合布局。


  

首先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。


  

通过给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 属性可以指定分栏偏移的栏数。


  

在源码中可以看到,给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})

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


  

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

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

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

总结:

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

你可能感兴趣的:(elementUI源码分析-02-layout布局)