element源码学习-Layout组件

573 阅读4分钟

前言

Layout布局尝试:

image-20200924002254419

大致由两个重要组件构成,el-row与el-col

row函数

row用来作为col的容器,通过render函数创建,上面有一些动态的classstyle,以及一个slot用来放置col,相对较为简单。

*render函数

参考官方手册

实际上提供了js操作渲染组件的能力(类似于React的思想),给予了组件更高的灵活性。使用JS可以完全代替模板功能(用 JavaScript 的 if/elsemap 来重写),以下两段代码等价:

<h1>{{ blogTitle }}</h1>
render: function (createElement) {
  return createElement('h1', this.blogTitle)
}

在这两种情况下,Vue 都会自动保持页面的更新,即便 blogTitle 发生了改变。

Vue 的模板实际上被编译成了渲染函数。

index.js

image-20200924002451523

这一块是按常规的组件注册和封装。

打开index.js,这里最后一句导出Row供我们import

中间的install方法则是把这个组件当成一个Vue的插件来使用,通过Vue.use()来使用该组件,install方法传递一个Vue的构造器,Element的所有组件都是一个对象{...}。

之后会看到里面有个render函数来创建组件的html结构,render方法的好处很大,使得创建html模板的代码更加简洁高效,而不是冗长的各种div标签堆叠,更类似于一种配置形式来创建html。

最后通过export default导出,而不是常用的单文件组件形式,因此必须提供install方法。

之前讲到过/* istanbul ignore next */,这个是一个npm代码覆盖率的工具istanbul,在写测试的时候,有些不必要的文件,在计算覆盖率时会被计算到,所以加上这句注释就可以忽略下面的代码。

row.js

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

它的style是一个计算属性,会根据gutter这一prop来改变,栏目之间的间隔,用来抵消col造成的两边的padding

  computed: {
    style() {
      const ret = {};

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

      return ret;
    }
  },

这里margin为负值的详细解释可看:element-layout分析

img

大概就是为了消除图中最左侧和最右侧的间隔。

class中的justify/align/type属性都是props,由用户选择性提供

    type: String,//是否采用flex布局
    justify: {//flex 布局下的水平排列方式
      type: String,
      default: 'start'
    },
    align: {// flex 布局下的垂直排列方式
      type: String,
      default: 'top'
    }

col函数

col.js

首先来看gutter这一style,与row中类似,是通过一个计算属性进行调整。

    if (this.gutter) {
      style.paddingLeft = this.gutter / 2 + 'px';
      style.paddingRight = style.paddingLeft;
    }

它的计算过程,涉及父实例。如果父实例不是el-row而是直接使用了el-col将以自身为准返回0,否则返回父组件已有的gutter,然后在此基础上增加padding。

一般的想法是col之间用margin来间隔,其实是不行的,而用padding来间隔就很简单,width按百分比来分配就行(box-sizing要设置为border-box)

  computed: {
    gutter() {
      let parent = this.$parent;
      while (parent && parent.$options.componentName !== 'ElRow') {//这里的componentName在row.js中有定义
        parent = parent.$parent;//如果没有被el-row包裹,继续向上查找有无el-row,直到找到el-row父组件为止。
      }
      return parent ? parent.gutter : 0;
    }
  },

*栅格系统

这里最完备也是最早最具有启发性的是boostrap提出的栅格系统

栅格系统用于通过一系列的行(row)与列(column)的组合来创建页面布局,你的内容就可以放入这些创建好的布局中。

  • “行(row)”必须包含在 .container (固定宽度)或 .container-fluid (100% 宽度)中,以便为其赋予合适的排列(aligment)和内补(padding)。
  • 通过“行(row)”在水平方向创建一组“列(column)”。
  • 你的内容应当放置于“列(column)”内,并且,只有“列(column)”可以作为行(row)”的直接子元素。
  • 类似 .row.col-xs-4 这种预定义的类,可以用来快速创建栅格布局。Bootstrap 源码中定义的 mixin 也可以用来创建语义化的布局。
  • 通过为“列(column)”设置 padding 属性,从而创建列与列之间的间隔(gutter)。通过为 .row 元素设置负值 margin 从而抵消掉为 .container 元素设置的 padding,也就间接为“行(row)”所包含的“列(column)”抵消掉了padding
  • 负值的 margin就是下面的示例为什么是向外突出的原因。在栅格列中的内容排成一行。
  • 栅格系统中的列是通过指定1到12的值来表示其跨越的范围。例如,三个等宽的列可以使用三个 .col-xs-4 来创建。
  • 如果一“行(row)”中包含了的“列(column)”大于 12,多余的“列(column)”所在的元素将被作为一个整体另起一行排列。
  • 栅格类适用于与屏幕宽度大于或等于分界点大小的设备 , 并且针对小屏幕设备覆盖栅格类。 因此,在元素上应用任何 .col-md-* 栅格类适用于与屏幕宽度大于或等于分界点大小的设备 , 并且针对小屏幕设备覆盖栅格类。 因此,在元素上应用任何 .col-lg-* 不存在, 也影响大屏幕设备。

image-20200924231838614

render函数部分

看下官网中属性的定义和效果

image-20200924233832568

再来看源代码

render(h) {
    let classList = [];
    let style = {};

    if (this.gutter) {
      style.paddingLeft = this.gutter / 2 + 'px';
      style.paddingRight = style.paddingLeft;
    }
// span 栅格占据的列数,通过 width 来实现
  // offset 栅格左侧的间隔格数,通过 margin-left 实现
  // push 栅格向右移动格数,通过 left 实现
  // pull 栅格向左移动格数,通过 right 实现
    ['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]}`
        );
      }
    });
 // 不同屏幕大小下的适配,传入数字的话只会影响 span,还可以通过传入对象进行更多的控制
  // xs <768px 响应式栅格数或者栅格属性对象
  // sm ≥768px 响应式栅格数或者栅格属性对象
  // md ≥992px 响应式栅格数或者栅格属性对象
  // lg ≥1200px 响应式栅格数或者栅格属性对象
    ['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);
  }

下面解释下['span', 'offset', 'pull', 'push']这几个的作用,span很好理解,占父容器的列数,对应scss代码如下:

[class*="el-col-"] {
  float: left;
  box-sizing: border-box;
}

.el-col-0 {
  display: none;
}

@for $i from 0 through 24 {
    //el-col-数字类型的类的宽度就是百分比
  .el-col-#{$i} {
    width: (1 / 24 * $i * 100) * 1%;
  }

  .el-col-offset-#{$i} {
    margin-left: (1 / 24 * $i * 100) * 1%;
  }

  .el-col-pull-#{$i} {
    position: relative;
    right: (1 / 24 * $i * 100) * 1%;
  }

  .el-col-push-#{$i} {
    position: relative;
    left: (1 / 24 * $i * 100) * 1%;
  }
}

  • [attribute*=value] 选择器,它选择了所有类名以el-col-开头的类.

  • offset实际上是margin-left(很好理解,相对左移就是偏移了),这可能会导致一行排列不下所有的col,会导致换行出现

  • el-col-pull则不同,仅仅只是相对原来的位置移动,不会造成挤下去换行的情况,而会造成不同col互相覆盖

js部分大量使用模板字符串而不是字符串拼接,达到简化代码的目的。

参考网站

Element分析(组件篇)——Layout

Element源码分析系列1一Layout(布局)

Bootstrap 栅格参数

Element.io - layout