美文网首页
elementUI源码分析-02-layout布局

elementUI源码分析-02-layout布局

作者: 范小饭_ | 来源:发表于2021-01-30 17:01 被阅读0次

    一、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。

    相关文章

      网友评论

          本文标题:elementUI源码分析-02-layout布局

          本文链接:https://www.haomeiwen.com/subject/xzlktltx.html