preface

During this period of time, I was studying the knowledge points of each front-end module. However, in the process of learning, I found a lack of sorting and thinking, which may be forgotten because of the weak grasp of knowledge points. Therefore, I hope to record my learning process in an article to deepen my understanding.

There are two main reasons for wanting to write about the element-Plus source code:

  1. Vue - next v3.0.0 - beta. 1The version was released in 2020-5-17, more than a year ago. As a daily main useVueTechnology stack students, it is necessary to learn how to use it better, so in learningElement-plusSource code process is also rightvue-nextUse learning.
  2. In the usual development process, we will often write some components, compare the components written by ourselves and the components of excellent projects in the community, reference and learn some skills of designing components.

This article is mainly a record and summary of my own learning. I hope you can correct my mistakes in the comments section. Thank you!

The preparatory work

  1. To learn the source code, let’s clone the element-plus code.
  2. To viewpackage.jsonWe can get throughscriptIn thewebsite-devScript startup project
  • inwebsite/webpack.config.jsAs can be seen in the WebPack configuration file, the default iswebsite/entry.jsAs an entry point, all the components are loaded and the project takes time to start.
  • performnpm run website-dev:playThat will bewebsite/play.jsAs an entrance.
  • All we have to do iswebsite/playAdd to folderindex.vueFile, here we’re going to debugLayoutComponents can be copied from the official demo here
  <template>
    <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>
  </template>
Copy the code
  1. After the project is launched, it is available in the browserSources > Page > webpack-internal:// > packagesSet breakpoints in the corresponding component file. To debug.

Layout components

Create a layout quickly and easily with the basic 24 columns

Firstly, Layout is divided into two components: ElRow and ElCol. The basic usage is as follows.

<el-row>
  <el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="8"><div class="grid-content bg-purple-light"></div></el-col>
  <el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
</el-row>
Copy the code

What function points are implemented

  1. aElRowIt’s a row, the horizontal space is equally divided24Share, by givingElColSet up thespanProperty sets the value [0,24] to define the proportion of an ElCol.
  2. By givingElRowSet up thegutterProperty to set the size of the column interval
  3. By givingElColSet up theoffset[0,24] property to set the current oneElColThe offset with respect to the left. I also set the percentage to 24.
  4. By givingElRowSet up thejustifyProperty to set the horizontal alignment. The optional value isjustify-contentThe optional value of.
  5. The responsive layout presets five response sizes: XS, SM, MD, LG and XL, in proportion to different screen widths.

Let’s look at each of these function points one by one.

24 columns

  • In Packages /col/ SRC /row.ts, setup returns the render function. If you are not familiar with this syntax please refer to the official documentation

  • You can create label names dynamically in the render function and set the class name el-row to make the element a Flex container.

  • Slot.default is of type to the function slot.default?.() using the optional chain operator

// row.ts
import { h } from 'vue'
  setup(props, { slots }) {
    return () = > 
        h(props.tag,
        {
          class: [
            'el-row'.// ...
          ]
          // ...
        },
        slots.default?.(),
        )
  }
Copy the code
  • inpackages/col/src/col.tsIn the usecomputedThe classList function dynamically generates a classList(an array) and then binds the array of class names to the elements.ElColAs aElRowSlots so every one of themElColIt’s a flex-item.
  • According to theprops.spanThe value of the generatedel-col-${props['span']}The name of the class.
    • inpackages/theme-chalk/src/col.scssClass names are predefined in.
    @for $i from 0 through 24# {{.$namespace}-col-#{$i} {
        max-width: (math.div(1.24) * $i * 100) * 1%;
        flex: 0 0 (math.div(1.24) * $i * 100) * 1%; }}Copy the code
import { defineComponent, computed, inject, h } from 'vue'
const ElCol = defineComponent({
  props: {span: {
      type: Number.default: 24,}},setup(props, { slots }) {
    const classList = computed(() = > {
      const ret: string[] = []
      const pos = ['span'.'offset'.'pull'.'push'] as const
      pos.forEach(prop= > {
        const size = props[prop]
        if (typeof size === 'number') {
          if(prop === 'span') ret.push(`el-col-${props[prop]}`)}})return ret
        return () = > h(
        props.tag,
        {
          class: ['el-col', classList.value],
          // ...
        },
        slots.default?.(),
    })
  }
})
Copy the code

gutter

Sets the interval between columns

  • To set spacing for each flex-item, set padding-left and padding-right for each flex-item.

  • Now we don’t need the spacing between the heads and the tailsElRowSet the left and right margin negative values on the container element to the following effect

Understand the principle and then look at the specific code implementation.

  • Inject gutter values into all child components via the provide function.
  • All you need to do in the ElRow component is remove the spacing between the beginning and the end, of size-gutter/2. Because each column is increasing left and rightgutter/2Padding of the size.
    • Use a computed function to compute the style object. Dynamically bind a Style object to the element’s style property.
export default defineComponent({
  name: 'ElRow'.props: {
    gutter: {
      type: Number.default: 0,},// ...
  }
  setup(props, { slots }) {
    // Returns the ComputedRef object
    const gutter = computed(() = > props.gutter)
    provide('ElRow', {
      gutter,
    })
    const style = computed(() = > {
      const ret = {
        marginLeft: ' '.marginRight: ' ',}if (props.gutter) {
        ret.marginLeft = ` -${props.gutter / 2}px`
        ret.marginRight = ret.marginLeft
      }
      return ret
    })
    return () = >
      h(
        props.tag,
        {
          style: style.value,
        },
        slots.default?.(),
      )
  }
Copy the code
  • Use the Inject function to accept attributes injected by a parent component (regardless of how many levels of nesting, parent or grandparent level). Because the value returned by computed is a Ref object when provided, the default value here is also set to an object that contains a value.
  • Also compute a style property and bind it to the element’s style.
setup(props,{slots}){
  const { gutter } = inject('ElRow', { gutter: { value: 0}})const style = computed(() = > {
    if (gutter.value) {
      return {
        paddingLeft: gutter.value / 2 + 'px'.paddingRight: gutter.value / 2 + 'px',}}return{}})return () = > h(
    props.tag,
    {
      // ...
      style: style.value,
    },
    slots.default?.(),
  )
}
Copy the code

offset

The offset setting works the same as 24 columns.

      const pos = ['span'.'offset'.'pull'.'push'] as const
      pos.forEach(prop= > {
        const size = props[prop]
        if (typeof size === 'number') {
          if(prop === 'span') ret.push(`el-col-${props[prop]}`)
          else if(size > 0) ret.push(`el-col-${prop}-${props[prop]}`)}})Copy the code
  1. acceptoffsetProps, dynamically generated based on the size of props. Offsetel-col-offset-${props['offset']}Class name and bind to the dynamic class property.
  2. These default class names are defined inpackages/theme-chalk/src/col.scss
@for $i from 0 through 24# {{.$namespace}-col-offset-#{$i} {
    margin-left: (math.div(1.24) * $i * 100) * 1%; }}Copy the code
  1. When you set the class name, you can apply the corresponding margin-left offset.

justify

Equivalent to setting context-content to the Flex container, the style is defined in Packages /theme-chalk/ SRC /row.scss.

Responsive layout

Support for raster number or raster property objects in different screen sizes

Five response sizes: XS, SM, MD, LG and XL. Below can set different grid number (proportion). Objects or numbers are supported.

    <el-row>
      <el-col :xs="{span: 4, offset: 2}" :sm="10"><div class="grid-content bg-purple"></div></el-col>
    </el-row>
Copy the code

So let’s see how this works in code.

  1. First of all, we define['xs', 'sm', 'md', 'lg', 'xl']Type, and then iterate over which type these props are passing.
  2. If the value is number, add it directlyel-col-xs-10The class name of the format.
  3. If the object type is{span:4,offset:2}Fetch all the keys under the object and add the class nameel-col-xs-offset-2Set props. Span toel-col-xs-4In the name of the class.
  4. The class name is defined inpackages/theme-chalk/src/col.scssThe use of thepackages/theme-chalk/src/mixins.scssdefineresMixins.
  • Size variables for different screen sizes are defined inpackages/theme-chalk/src/common/var.scssIn the$--breakpointsCSS variables.
  setup(props, { slots }) {
    const classList = computed(() = > {
      const ret: string[] = []
      const sizes = ['xs'.'sm'.'md'.'lg'.'xl'] as const
      sizes.forEach(size= > {
        if (typeof props[size] === 'number') {
          ret.push(`el-col-${size}-${props[size]}`)}else if (typeof props[size] === 'object') {
          const sizeProps = props[size]
          Object.keys(sizeProps).forEach(prop= >{ ret.push( prop ! = ='span' ? `el-col-${size}-${prop}-${sizeProps[prop]}` : `el-col-${size}-${sizeProps[prop]}`,)})}})}return () = > h(
      props.tag,
      {
        class: ['el-col', classList.value],
      },
      slots.default?.(),
    )
  }
Copy the code

conclusion

  • Mainly involvesvue-nextSyntax has the following points:setup,H Render function syntax,computed,provide,injectEtc.
  • Columns, gutter, etc., are implemented with predefined class names. The class name is then dynamically generated based on the values passed to the props. If a match is found, the corresponding style is used.
    • There are several ways to use dynamic class names. Bind strings, objects, and arrays.
    • Concatenation of dynamic class names is required in some caseskeyorvalue, can be flexibly adjusted to make the code more concise and flexible
<div
        :class="[ ['class-1','class-2'], {'class-3':true}, native ? '' : 'scrollbar__wrap--hidden-default' ]"
     />
Copy the code