This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together.

One, the introduction


Hello, I’m Wang Miao. Recently, I have also learned some source code of the Element-UI framework, and some of the design methods and ideas are also very worthy of reference. I also want to share with you the common progress, so that our components are more elegant.

Second, preparation


  • Before we look at the source code for the element-UI component, we need to take a look at Vue’s global method, vue.extend.

  • Elder-ui source code. If not, you can find elder-UI/Packages/Message in the project dependencies.

Third, the body


3.1 the Vue. The extend

  • Parameters:
-   `{Object} options`
Copy the code
  • Usage:

    Using the base Vue constructor, create a “subclass.” The parameter is an object that contains component options.

    The data option is a special case, note – it must be a function in vue.extend ()

    <div id="mount-point"></div>
    Copy the code
    Var Profile = Vue. Extend ({template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: Function () {return {firstName: 'Walter', lastName: 'White', alias: 'Heisenberg'}}}) // Create Profile instance and mount it to an element. new Profile().$mount('#mount-point')Copy the code

The vue template can be rendered to any DOM element. The options parameter can be passed in as shown in the demo, or the vue template can be imported via import.

import Main from './main.vue';
let Profile = Vue.extend(Main);
new Profile().$mount('#mount-point')
Copy the code

Parameters can also be passed in during the creation of a Profile instance. This parameter is a supplementary parameter to the imported instance. Vue merges the parameters with the template.

import Main from './main.vue';
let Profile = Vue.extend(Main);
const templateExtend = {
    data:{
        id:1
    }
}
new Profile(templateExtend).$mount('#mount-point')
Copy the code

When mounting, the parameters can be optionally passed in. If none is passed in, you need to manually add elements from the instance $EL to the DOM.

import Main from './main.vue';
let Profile = Vue.extend(Main);
const instance = new Profile().$mount()
document.body.appendChild(instance.$el)
Copy the code

That’s the extend argument and how to use it.

3.2 element – the UI

When we enter the Element-UI/Packages directory of the Element-UI, we can actually see that all the component directory structures are as shown below

├─ └─ SRC ├─ main.js // Create some operation logic ├─ main.vue // template of message componentCopy the code
3.2.1 Structure of main.js file
import Vue from 'vue'; import Main from './main.vue'; let MessageConstructor = Vue.extend(Main); let instance; let instances = []; let seed = 1; Close = function(seed,userOnClose){} const Message = function(options){// create message. close = function(seed,userOnClose){ Message.closeAll = function(){} // Destroy all Message instances export default MessageCopy the code

As you can see, the Message component is also very clean, with only create and destroy operations. Let’s take a look at what it looks like inside.

Message

function(options){ let userOnClose = options.onClose; // pass the close method let id = 'message_' + seed++; Options. onClose = function() {message. close(ID, userOnClose); }; instance = new MessageConstructor({ data: options }); instance.id = id; instance.$mount(); / / instantiate the document. The body. The appendChild (instance. $el); / / add dom let verticalOffset = options. Offset | | 20; ForEach (item => {verticalOffset += item.$el.offsetheight + 16; }); instance.verticalOffset = verticalOffset; instance.visible = true; instances.push(instance); return instance; }Copy the code

This method creates a unique ID for the current instance (removed by the unique ID), passes the close callback passed in by the user to message.close, appends $el to the DOM, and mounts verticalOffset and Visible to the instance.

VerticalOffset: After the instantiation is complete, the height of the current instance should be calculated based on all the existing instances. This can be used to set the top value in the template to display multiple messages in order.

Visible: Use this. Visible in the template to show and hide, and use vue-Transition to make the gradient effect.

Message.close

function(id, userOnClose) { let len = instances.length; let index = -1; let removedHeight; for (let i = 0; i < len; i++) { if (id === instances[i].id) { removedHeight = instances[i].$el.offsetHeight; index = i; if (typeof userOnClose === 'function') { userOnClose(instances[i]); } instances.splice(i, 1); break; } } if (len <= 1 || index === -1 || index > instances.length - 1) return; for (let i = index; i < len - 1 ; i++) { let dom = instances[i].$el; dom.style['top'] = parseInt(dom.style['top'], 10) - removedHeight - 16 + 'px'; }};Copy the code

Code parsing: The main logic of this section is to find out the instance to be removed by ID, determine whether the user passed in the custom userClose event and call this event, and adjust the height of all instances that are not closed by looping instances.

Message.closeAll

for (let i = instances.length - 1; i >= 0; i--) {
    instances[i].close();
}
Copy the code

Code parsing: Loop through the list of S instances, calling the close method

3.2.2 Main.vue Template Structure (Simplified)
<template> <transition name="el-message-fade" @after-leave="handleAfterLeave"> <div:class="['el-message']" v-show="visible"> </div> </transition> </template> <script type="text/babel"> export default { data() { return { visible: false, message: '', duration: 3000, onClose: null, verticalOffset: 20, timer: null, }; }, computed: { positionStyle() { return { 'top': `${ this.verticalOffset }px` }; } }, methods: { handleAfterLeave() { this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, close() { this.visible = false; this.onClose(this); }, clearTimer() { clearTimeout(this.timer); }, startTimer() { if (this.duration > 0) { this.timer = setTimeout(() => { this.close(); }, this.duration); } }, }, mounted() { this.startTimer(); }}; </script>Copy the code

Code parsing: Call the startTimer method after the component is created, execute the close method to set Visible to false three seconds later, and call the onClose method passed in main.js to move the component out of the array and set the height of the other instances. The handleAfterLeave method is called to remove the current DOM after Visible is false and animation execution is complete.

We could also put setTimeout in main.js and use the Promise feature to mount resolve into the instance

Four, feeling

Today we mainly share the use of Vue. Extned method, global component implementation ideas. Of course, this is only one way to implement global components, and there are many practical tips to explore, as well as ideas for implementing other components.

It is my first time to write a technical article. I am a little weak in language description. I hope you can point out any omissions or deficiencies in the article

I heard that you like to praise, this year’s year-end award to get soft 😍