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