Серия анализа исходного кода элемента 1-Layout (макет)

внешний интерфейс исходный код контейнер Element

мотивация

Я чувствую, что текущее развитие бизнеса, если это не очень специальное требование, может в основном найти и использовать компоненты в соответствующей библиотеке компонентов.Таким образом, написание кода становится вызывающим компонентом, но он скрывает идеи в компоненте, таким образом ослабляя способность программирования.Поэтому я хочу написать такую ​​серию анализов, чтобы мотивировать себя на глубокий анализ принципов работы компонентов и улучшить мою способность понимать код.Я думаю, что я должен что-то записать.Если я просто не могу читать ручку , быстро забуду, так что планирую продолжать писать это.

Структура исходного кода элемента

Портал официального сайтакликните сюда, основной каталог выглядит следующим образом

Исходный код компонента размещен вpackageПод содержанием,srcЕсть некоторые функции инструмента (некоторые компоненты будут использовать эти функции) и код, связанный с интернационализацией, введитеpackageВ каталоге это исходный код всех компонентов

Обратите внимание, что эти папки содержат только js или vue, а файлы стилей всех компонентов находятся внизу.theme-chalkВ папке вся структура проекта еще очень четкая

Вёрстка (layout) анализ исходного кода

  • <el-row>Анализ исходного кода

Сначала введите, чтобы открыть официальный сайт для просмотраLayoutПо описанию соответствующей части установлено, что основных компонентов всего 2:el-row,el-col, Эти два представляют контейнер строки и контейнер столбца внутри, аналогичноbootstrapизcolа такжеrow, сначала смотримel-rowреализация, введитеpackageвнутриrowпапка, которая представляет собойsrcпапка иindex.jsдокумент

Открытьindex.js, последнее предложение здесь полученоRowдля насimport, а серединаinstallМетод состоит в том, чтобы использовать этот компонент в качестве Vue Plug-In, черезVue.use()Чтобы использовать этот компонент, метод установки передает конструктор Vue, все компоненты Element представляют собой объект {...}, который имеетrenderфункция для создания html-структуры компонента,renderПреимущества метода велики, делая код для создания html-шаблонов более лаконичным и эффективным, а не нагромождение различных тегов div, что больше похоже на форму конфигурации для создания html.export defaultЭкспорт вместо обычно используемой формы компонента с одним файлом, поэтому необходимо указать метод установки.

import Row from './src/row';
/* istanbul ignore next */
Row.install = function(Vue) {
  //全局注册该组件(常用的组件最好全局注册)
  Vue.component(Row.name, Row);
};
export default Row;

Тут на самом деле есть два способа использования компонентов.Один-это использовать компоненты как плагины,но прописывать компоненты после импорта напрямую.Пример кода на официальном сайте такой,или не надо регистрироваться как глобальный компонент .

import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';
Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
/* 或写为
 * Vue.use(Button)
 * Vue.use(Select)
 */
new Vue({
  el: '#app',
  render: h => h(App)
});

Введите нижеsrc/row.jsВо-первых, общая структура кода такова, непосредственно экспортирует объект, который содержит различные элементы конфигурации компонента.

export default {
    ...
}

Объем кода всего компонента невелик, далее подробный комментарий

export default {
  //组件名称,注意是驼峰命名法,这使得实际使用组件时短横线连接法<el-row>和驼峰法<ElRow>都可以使用
  name: 'ElRow',
  //自定义属性(该属性不是component必需属性),重要,用于后面<el-col>不断向父级查找该组件
  componentName: 'ElRow',
  //组件的props
  props: {
    //组件渲染成html的实际标签,默认是div
    tag: {
      type: String,
      default: 'div'
    },
    //该组件的里面的<el-col>组件的间隔
    gutter: Number,
    /* 组件是否是flex布局,将 type 属性赋值为 'flex',可以启用 flex 布局,
    *  并可通过 justify 属性来指定 start, center, end, space-between, space-around
    *  其中的值来定义子元素的排版方式。
    */ 
    type: String,
    //flex布局的justify属性
    justify: {
      type: String,
      default: 'start'
    },
    //flex布局的align属性
    align: {
      type: String,
      default: 'top'
    }
  },

  computed: {
    //row的左右margin,用于抵消col的padding,后面详细解释,注意是计算属性,这里通过gutter计算出实际margin
    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);
  }
};

Поговорим о вычисляемых свойствахsytle(), здесь черезgutterАтрибут вычисляет левое и правое поля этого компонента, и это отрицательное число. Это немного сбивает с толку. Рисунок выше объясняет это. Прежде всегоgutterФункция состоит в том, чтобы столбец в строке генерировал интервал,Но обратите внимание, что левые и крайне прямые стороны контейнера не разделены

Вышеприведенное изображение является окончательной принципиальной схемой, черная рамка<el-row>Ширина диапазона, т.<el-col>Компонент, представленный в следующем разделе, ширина этого компонента на самом деле<el-row>процент иbox-sizingдаborder-box,УведомлениеgutterСвойства определены на родительском<el-row>Вкл., столб ребенка проходит через$parentВы можете получить этот атрибут, а затем дать<el-col>распространятьpadding-leftа такжеpadding-right, Таким образом, каждый столбец имеет левое и правое отступы. На приведенном выше рисунке каждый столбец занимает 25% ширины, а ширина поля в два раза больше заполнения столбца, но обратите внимание, что крайний левый и правый отступы отсутствуют. правильно, тогда возникает проблема, как устранить крайний левый и крайний правый отступ?Вот<el-row>Отрицательное поле работает.Если стиль вышеуказанного вычисляемого свойства не установлен, будет отступ столбца с левой и правой сторон, поэтому отрицательное поле здесь смещает отступ столбца, а значение равно-gutter/2+'px'

Обратите внимание, что если вы сначала посмотрите на картинку выше, общая идея состоит в том, чтобы использовать поля для разделения столбцов, но это невозможно, но очень просто использовать отступы для разделения, а ширина выделяется в процентах (box-sizing должно быть установлено значение border-box )

Ниже объясняется функция рендеринга, которая, наконец, возвращаетrender, Эта функция имеет 3 параметра, первый параметр — это имя тега html (имя тега, которое в конечном итоге будет отображаться на веб-странице), а второй параметр — это данные, содержащие атрибуты, связанные с шаблоном.объект, который имеет довольно много свойств, связанных с шаблоном, а именно:

{
  // 和`v-bind:class`一样的 API
  // 接收一个字符串、对象或字符串和对象组成的数组
  'class': {
    foo: true,
    bar: false
  },
  // 和`v-bind:style`一样的 API
  // 接收一个字符串、对象或对象组成的数组
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 正常的 HTML 特性
  attrs: {
    id: 'foo'
  },
  // 组件 props
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器基于 `on`
  // 所以不再支持如 `v-on:keyup.enter` 修饰器
  // 需要手动匹配 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅对于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽格式
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其他组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其他特殊顶层属性
  key: 'myKey',
  ref: 'myRef'
}

Обратите особое внимание на третий параметр, который представляетдочерний узел,ЯвляетсяStringилиArray,КогдаStringКогда представляет содержимое текстового узла, в настоящее время это текстовый узел, если онArray, который является дочерним узлом, каждое значение в массиве является функцией параметра рендеринга

[
    //文本节点
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
]

Посмотрите на третий параметр функции рендеринга выше:this.$slots.default, Смысл здесь в том, чтобы получить содержимое компонента, который не является именованным слотом.Атрибут по умолчанию включает все узлы, которые не входят в именованный слот.Для следующего кода функция рендеринга поставит<el-row>так же как<h1>test<h1>Визуализируются вместе как его дочерние узлы

<el-row>
    <h1>test<h1>
    <slot name='t'>t1</slot>
</el-row>

Наконец, объясните код, связанный со стилем,row.scssПутьpackages/theme-chalk/src/row.scss, код типа scss, класс в рендере такой

class:[
        'el-row',
        this.justify !== 'start' ? `is-justify-${this.justify}` : '',
        this.align !== 'top' ? `is-align-${this.align}` : '',
        { 'el-row--flex': this.type === 'flex' }
      ],

здесьel-rowКласс на самом деле не определен, вы можете добавить его при написании кода самостоятельно, это то, что используется на официальном сайте, последние несколько управляют flex-макетом.<el-row>По умолчанию заполняется ширина родительского контейнера, а высота автоматически адаптируется.

  • <el-col>Анализ исходного кода
    Использование col также очень просто, а именно:span,offset,pull,pushРавные атрибуты
<el-col :span="6" :offset="6"><div class="grid-content bg-purple"></div></el-col>

Войтиpackage/colПроверьте, код col чуть длиннее, основная дополнительная логика — управление адаптацией (@media screen)

export default {
  //组件名称
  name: 'ElCol',
  props: {
    //组件占父容器的列数,总共24列,如果设置为0则渲染出来display为none
    span: {
      type: Number,
      default: 24
    },
    //最终渲染出的标签名,默认div
    tag: {
      type: String,
      default: 'div'
    },
    //通过制定 col 组件的 offset 属性可以指定分栏向右偏移的栏数
    offset: Number,
    //栅格向右移动格数
    pull: Number,
    //栅格向左移动格数
    push: Number,
    //响应式相关
    xs: [Number, Object],
    sm: [Number, Object],
    md: [Number, Object],
    lg: [Number, Object],
    xl: [Number, Object]
  },

  computed: {
    //获取el-row的gutter值
    gutter() {
      let parent = this.$parent;
      //不断通过获取父元素直到找到el-row元素位置,注意这里的技巧,componentName实际
      //是el-row组件设置的一个自定义属性,用来判断是否是el-row组件
      while (parent && parent.$options.componentName !== 'ElRow') {
        parent = parent.$parent;
      }
      return parent ? parent.gutter : 0;
    }
  },
  render(h) {
    let classList = [];
    let style = {};
    //通过gutter计算自己的左右2个padding,达到分隔col的目的
    if (this.gutter) {
      style.paddingLeft = this.gutter / 2 + 'px';
      style.paddingRight = style.paddingLeft;
    }
    //处理布局相关,后面详细介绍
    ['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'].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-#{$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-Класс в начале, плюс float и border-box, float однозначно незаменим для горизонтальной верстки, а потом посмотрите на цикл for, где в игру вступает мощь scss.Если используется только css, то количество кода нужно умножить на 24,el-col-数字Ширина класса типа в процентах, следующиеoffsetв реальностиmargin-left, что может привести к тому, что строка не будет соответствовать всем столбцам, что приведет к появлению новой строки, аel-col-pullОн другой, он просто перемещается относительно исходного положения, он не вызовет ситуации сдавливания и закручивания, а заставит разные столбцы накрывать друг друга

Обратите внимание, что в приведенной выше части js для упрощения кода используется множество строк шаблона вместо объединения строк.