preface

This example takes [email protected] as a reference, analyzes its role in actual development from some common components according to the Vue knowledge points involved in them.

Directory Structure

Elemental-ui /lib holds webpack files.

Elder-ui /packages houses all the components provided by Elder-UI.

SRC provides a few tools that are needed globally: JS methods, mixins, directives, etc. (I’ll pick one or more of these later).

Element-ui/Transitions provides a global animation component that Element-UI provides.

The element-ui/utils provides some common utility methods (I’ll pick one or more below).

1. CSS and SCSS

Element-ui is built using SCSS, where each component’s CSS is located under Theme-Chalk under Packages, lib is the result of packaging and compilation, and SRC is the source code. With the exception of public SCSS files and index.scss, each SCSS file name corresponds to a component. These component SCSS files are introduced in index.scSS.

1.1 the config. SCSS

Into the element – the UI/packages/theme – chalk/SRC/mixins/config. The SCSS, we can see the following content:

$namespace: 'el';
$element-separator: '__';
$modifier-separator: The '-';
$state-prefix: 'is-';
Copy the code

There’s a concept here called CSS BEM. BEM stands for block, element, or modifier, and is a CSS Class naming method proposed by the Yandex team. By reading the source code of Element-UI, I learned the basic concepts and usage scenarios of BEM.

1.2, mixins. SCSS

Into the element – – chalk/SRC/UI/packages/theme mixins/mixins SCSS, we can see this file defines some mixed with commonly used components. The familiar blends will not be posted here, but let’s look at a few key blends:

@import "function";
/* Some code is omitted */

/* BEM -------------------------- */
 With / / block
@mixin b($block) {
  $B: $namespace+The '-'+$block! global; . # {$B} {
    @content; }}// Element mixin
@mixin e($element) {
  $E: $element! global;$selector: &;
  $currentSelector: "";
  @each $unit in $element {
    $currentSelector: # {$currentSelector + "." + $B + $element-separator + $unit + ","};
  }

  @if hitAllSpecialNestRule($selector) {
    @at-root {
      #{$selector#} {{$currentSelector} {
          @content; }}}}@else {
    @at-root {
      #{$currentSelector} {
        @content; }}}}// Modifiers are mixed in
@mixin m($modifier) {
  $selector: &;
  $currentSelector: "";
  @each $unit in $modifier {
    $currentSelector: # {$currentSelector + & + $modifier-separator + $unit + ","};
  }

  @at-root {
    #{$currentSelector} {
      @content; }}}// State is mixed
@mixin when($state) {
  @at-root# {{&.$state-prefix + $state} {
      @content; }}}// Pseudo-class mixin
@mixin pseudo($pseudo) {
  @at-root# # # {and} {' : {$pseudo}'} { @content } }Copy the code

I was lucky enough to find the ElementUI team’s design ideas for this code in Zhihu, so I won’t go into details here. My previous use of SASS was mostly confined to nested syntax, and at best I used the syntax features of SASS such as mixin, interpolation, & abbreviations. Through reading these codes, I learned the advanced uses of SCSS. The main points include: using the @at-root directive can generate nested SASS code to the top of the document, without slowing down the browser’s parsing speed while maintaining the original readability of the code, and @content specifies where to insert additional imported content. In _button. SCSS of the same path, I was curious and found that all elder-UI uses mixins/button. SCSS through retrieval. Then I consulted the SASS document and learned that it uses another feature called “split sound”.

If you need to import SCSS or Sass files but do not want to compile them as CSS, you simply add an underscore to the file name. This will tell Sass not to compile these files, but you do not need to add an underscore to the import statement.

Once we had this knowledge, we tried to write our own CSS code using these rules

// Suppose I configured the namespace to be my
@include b(app) {
    display: flex;
    @include e(header) {
        height: 200px;
        width: 100%;
    }
    
    @include e(body) {
       height: 1000px;
       width: 100%;
    }
    
    @include m(mobile) {
        width:100%
    }
    @include m(pc) {
            width: 1200px;
            margin:0px auto;
    }
    @include when(error) {
        border: 1px solid red;
    }
    
    @include pseudo(before){
        content: ' ';
        width: 10px;
        height: 10px; }}Copy the code

The generated code looks like this:

.my-app {
   display: flex;  
}
.my-app__header{
   height: 200px;
   width: 100%;
}

.my-app.is-error{
    border: 1xp solid red;
}

.my-app--mobile{
      width:100%
}

.my-app--pc{
     width: 1200px;
     margin:0px auto;
}

.my-app__body{
     height: 1000px;
      width: 100%;
}
Copy the code

The advantage of this is that our styles are generated in the root directory, which ensures that SCSS has a hierarchical structure at the code level and also takes into account the parsing performance of the browser. This is a better WAY to organize CSS. As you can see from the ElementUI team’s post on Zhihu, they’ve already taken a lot of complicated boundary cases into account when designing mixins, so we’re going to go straight to the abstraction right now, hahaha, mom won’t have to worry about my CSS anymore. I for CSS in the actual development one of the biggest confusion is due to the lack of reasonable organization of CSS so write CSS more disorderly (may be I don’t pay attention to the development of ability in CSS) caused, for some similar CSS can write many times, code redundancy, believe in the future will be for my CSS coding style has very big improvement.

2. Broadcast and Dispatch

Broadcast exists in vu1.x and is deprecated in Vue2, but there are times when there is a need to broadcast events to our component tree. ElementUI implements such a mixin in elder-ui/SRC /mixins/ Emitter.js as follows:

function broadcast(componentName, eventName, params) {
  this.$children.forEach(child= > {
    var name = child.$options.componentName;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else{ broadcast.apply(child, [componentName, eventName].concat([params])); }}); }export default {
  methods: {
      // Broadcast events to the parent component
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;
    
      while(parent && (! name || name ! == componentName)) { parent = parent.$parent;if(parent) { name = parent.$options.componentName; }}if(parent) { parent.$emit.apply(parent, [eventName].concat(params)); }},// Broadcasts feature events to child components
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params); }}};Copy the code

$options $children $parent $options $children $parent $options

Initialization options for the current Vue instance. It is useful when you need to include a custom property in your options

Therefore, we only need to provide componentName when we use it. $parent = $parent

Parent instance, if the current instance has one.

$children: $children

A direct child of the current instance. Note that $children does not guarantee order and is not responsive. If you find yourself trying to use $children for data binding, consider using an Array with V-for to generate child components, and using Array as the real source.

For example 🌰 :

<script> // Import emitter from 'elemental-ui/SRC /mixins/emitter'; export default { name: 'ElInput', componentName: 'ElInput', mixins: [Emitter], created() {this.$on('inputSelect', this.select); }},Copy the code

When we need to use these kinds of communication methods, we just need to mix in Emitter. In practice, I personally recommend not using $children and $parent to directly modify child or parent methods or properties, because this creates strong coupling and almost 100% of our code will change if we change the component structure in the template. Therefore, for the communication method of components, readers can refer to the forum and adopt the recommended method. However, this approach has certain reference value when we implement our own component library.

Inject and provide

Provide and Inject are defined as follows:

The provide option allows us to specify the data/methods we want to provide to future generations of components. Then in any descendant component, we can use the Inject option to receive the specified ones that we want to add to the instance. Advantages: Descendant components do not need to know where the injected property came from, and ancestor components do not need to know which descendant components use the property it provides. Cons: It couples the components in your application to their current organization, making refactoring more difficult. Also, the provided property is non-reactive.

The approach I use most in real development is to use provide and Inject for component communication. Through the upper component directly injected into the component reference (response type, whether or not you on the program and no substantial effect compared with $children and $parent, at the time of design always considering the component hierarchy, because not because components nested hierarchy variable depth and cause code changes, thus has certain practical value. In ElementUI, most of the input controls in Form and FormItem and in FormItem are present.

Export default {name: 'ElForm', componentName: 'ElForm', provide() {return {ElForm: this}; }},Copy the code
<script> // Unnecessary code omitted import AsyncValidator from 'async-validator'; export default { name: 'ElFormItem', componentName: 'ElFormItem', mixins: [emitter], provide() { return { elFormItem: this }; }, inject: ['elForm'], methods: { validate(trigger, callback = noop) { this.validateDisabled = false; const rules = this.getFilteredRule(trigger); if ((! rules || rules.length === 0) && this.required === undefined) { callback(); return true; } this.validateState = 'validating'; const descriptor = {}; if (rules && rules.length > 0) { rules.forEach(rule => { delete rule.trigger; }); } descriptor[this.prop] = rules; const validator = new AsyncValidator(descriptor); const model = {}; model[this.prop] = this.fieldValue; validator.validate(model, { firstFields: true }, (errors, invalidFields) => { this.validateState = ! errors ? 'success' : 'error'; this.validateMessage = errors ? errors[0].message : ''; callback(this.validateMessage, invalidFields); this.elForm && this.elForm.$emit('validate', this.prop, ! errors, this.validateMessage || null); }); }}},Copy the code

Here I use ElInput as an example:

Export default {name: 'ElInput', componentName: 'ElInput', inheritAttrs: false, inject: {elForm: { default: '' }, elFormItem: { default: '' } }, computed: { _elFormItemSize() { return (this.elFormItem || {}).elFormItemSize; }, validateState() { return this.elFormItem ? this.elFormItem.validateState : ''; }, needStatusIcon() { return this.elForm ? this.elForm.statusIcon : false; }, } } </script>Copy the code

In this scenario, the use of dependency injection components because they do not need additional associated external dependencies (such as external props or Vuex or EventBus), the most is to join a judgment for the data dependency injection (as long as the external dependence I can run normally, if you provide is not correct, my current components do not provide this functionality, But it does not affect my overall operation), the impact on the current component is relatively small. When used at the right time, it is an elegant way to communicate between components.

4, summarize

This article explains some advanced uses of SCSS and common uses of CSS BEM and Vue APIS through ElementUI. As it covers many knowledge points, it may be too long and boring to explain in one article. I will analyze and introduce some other knowledge points involved in the next chapter. Due to the limited level of the author, it is inevitable that there will be mistakes in the writing process. If there are any mistakes, please correct them. Your opinions will help me make better progress. If you want to reprint this article, please contact the author at [email protected]🥰