preface

Element – Well-known in the industry

Through this series of articles, I am ready to share the analysis of the source code of UI framework such as element2-message and Element Plus, and connect the knowledge points of Vue2, Vue3 foundation, TypeScript, Jest and Monorepo project management mode to familiarize myself with them.

Structure analysis

After the Clone project on Github, Packages/Message is our target directory.

The directory is simple, after index.js is imported into main.js, it is exposed, then imported in the population file, and then mounted on vue.prototype as alias $message.

main.vue

<template>
  <transition name="el-message-fade" @after-leave="handleAfterLeave">
    <div
      :class="[ 'el-message', type && !iconClass ? `el-message--${type}` : '', center ? 'is-center' : '', showClose ? 'is-closable' : '', customClass, ]"
      :style="positionStyle"
      v-show="visible"
      @mouseenter="clearTimer"
      @mouseleave="startTimer"
      role="alert"
    >
      <i :class="iconClass" v-if="iconClass"></i>
      <i :class="typeClass" v-else></i>
      <slot>
        <p v-if=! "" dangerouslyUseHTMLString" class="el-message__content">
          {{ message }}
        </p>
        <p v-else v-html="message" class="el-message__content"></p>
      </slot>
      <i
        v-if="showClose"
        class="el-message__closeBtn el-icon-close"
        @click="close"
      ></i>
    </div>
  </transition>
</template>

<script type="text/babel">
const typeMap = {
  success: "success".info: "info".warning: "warning".error: "error"};export default {
  data() {
    return {
      visible: false.// Message text
      message: "".// Display time in milliseconds. If this parameter is set to 0, it will not be automatically closed
      duration: 3000./ / type: success/warning/info/error
      type: "info".// Create a custom class-icon class name
      iconClass: "".// Define the class name
      customClass: "".// The closed callback function with the closed message instance
      onClose: null.// Whether to display the close button
      showClose: false.// This component is used to judge the end of the display flag
      closed: false.// Height, used when multiple instances are present at the same time, add a certain height down
      verticalOffset: 20.// Timer, three seconds to close this instance
      timer: null.// Whether to treat the message attribute as an HTML fragment
      dangerouslyUseHTMLString: false.// Center display
      center: false}; },computed: {
    // Display different style class names by type
    typeClass() {
      return this.type && !this.iconClass
        ? `el-message__icon el-icon-${typeMap[this.type]}`
        : "";
    },
    // The distance from the top
    positionStyle() {
      return {
        top: `The ${this.verticalOffset}px`}; }},watch: {
    // Why do we need more flags?
    closed(newVal) {
      if (newVal) {
        this.visible = false; }}},methods: {
    // Animation is done destroying el
    handleAfterLeave() {
      this.$destroy(true);
      this.$el.parentNode.removeChild(this.$el);
    },
    // Close the operation and execute the callback if there is one
    close() {
      this.closed = true;
      if (typeof this.onClose === "function") {
        this.onClose(this); }},// Clear timer, mouse over it, does not disappear
    clearTimer() {
      clearTimeout(this.timer);
    },
    // The instance starts the timer and resets it as soon as the mouse enters
    startTimer() {
      if (this.duration > 0) {
        this.timer = setTimeout(() = > {
          if (!this.closed) {
            this.close(); }},this.duration); }},// Listen for the ESC key
    keydown(e) {
      if (e.keyCode === 27) {
        // Esc closes the message
        if (!this.closed) {
          this.close(); }}}},mounted() {
    this.startTimer();
    document.addEventListener("keydown".this.keydown);
  },
  beforeDestroy() {
    document.removeEventListener("keydown".this.keydown); }};</script>
Copy the code

The above code is main.vue source code, clear and clear. One of the little things THAT I haven’t noticed before is that if you mouse over it, the instance will always be displayed, and that’s done in the code by @mouseEnter =”clearTimer”. $destroy(true) this.$destroy(true) ¶

Share the source code for $destroy in Vue2

You can see that no arguments are received in $destory and the beforeDestroy, destroyed methods are called in order in the function.

main.js

In contrast to main.vue, the main.js file is the most important.

import Vue from 'vue';
import Main from './main.vue';
import { PopupManager } from 'element-ui/src/utils/popup';
import { isVNode } from 'element-ui/src/utils/vdom';
let MessageConstructor = Vue.extend(Main);

/ / MessageConstructor instance
let instance;
let instances = [];
// Set the id of each instance in the Instances array
let seed = 1;

const Message = function(options) {
  // The server cannot use it.
  if (Vue.prototype.$isServer) return;
  // The argument passed in when this.$message is called
  options = options || {};
  // If this.$message(" test popup ") is used, it can be converted directly
  if (typeof options === 'string') {
    options = {
      message: options
    };
  }
  // Close the callback
  let userOnClose = options.onClose;
  // Instance id
  let id = 'message_' + seed++;
  // Add close callback
  options.onClose = function() {
    Message.close(id, userOnClose);
  };
  // Create a message instance
  instance = new MessageConstructor({
    data: options
  });
  instance.id = id;
  // Receive the render function
  if (isVNode(instance.message)) {
    // https://cn.vuejs.org/v2/guide/render-function.html?  
    // The default slot accepts an array of nodes
    instance.$slots.default = [instance.message];
    instance.message = null;
  }
  // Manually mount
  instance.$mount();
  document.body.appendChild(instance.$el);
  // Drop 16px each for multiple instances
  let verticalOffset = options.offset || 20;
  instances.forEach(item= > {
    verticalOffset += item.$el.offsetHeight + 16;
  });
  instance.verticalOffset = verticalOffset;
  // Display an example
  instance.visible = true;
  / / deal with z - index
  instance.$el.style.zIndex = PopupManager.nextZIndex();
  // Into the instance array
  instances.push(instance);
  return instance;
};
/ / take alias
['success'.'warning'.'info'.'error'].forEach(type= > {
  Message[type] = options= > {
    if (typeof options === 'string') {
      options = {
        message: options
      };
    }
    options.type = type;
    return Message(options);
  };
});
// Close the deleted instance
Message.close = function(id, userOnClose) {
  // Number of instances
  let len = instances.length;
  let index = -1;
  // Find the instance subscript to close
  let removedHeight;
  for (let i = 0; i < len; i++) {
    if (id === instances[i].id) {
      removedHeight = instances[i].$el.offsetHeight;
      index = i;
      // If there is a close callback
      if (typeof userOnClose === 'function') {
        userOnClose(instances[i]);
      }
      instances.splice(i, 1);
      break; }}if (len <= 1 || index === -1 || index > instances.length - 1) return;
  // Scale back the height from the deleted subscript
  for (let i = index; i < len - 1 ; i++) {
    let dom = instances[i].$el;
    dom.style['top'] =
      parseInt(dom.style['top'].10) - removedHeight - 16 + 'px'; }}; Message.closeAll =function() {
  for (let i = instances.length - 1; i >= 0; i--) { instances[i].close(); }};export default Message;

Copy the code

The logic for main.js is to define the Message function –> alias –> add the close (method for deleting an instance) /closeAll method; The Message function logic is: determine assignment options- > add close callback -> Create MessageConstructor instance -> Assign instance ID -> Manually mount -> UI response -> Show instance -> Place instance array

The PopupManager referenced at the beginning is used to set the z-index. See this article about using PopUp in Element. The VNode method is introduced to determine whether a node is a render function;

About Vue. Extend is not used much when doing Vue project, so this time just review this knowledge point. Vue.extend()

As you can see from the vue.extend source code, when extendOptions were merged, they were not unmapped, so after doing the following, options is mapped to the same address as data in this instance. You can see this by printing options.

About manual mounting:Vue.$mount(); Manually mount an unmounted instance using vm.$mount(). If no elementOrSelector parameter is provided, the template will be rendered as an element outside the document, and you must insert it into the document using the native DOM API.

Reading source code is a good way to learn, write this article is also to record to share their own experience, I hope you big guy more advice. The relevant source code, has been uploaded to Github, please consult yourself. element-source-code