This article participated in the weekly reading activity of source code jointly initiated by the public account @Ruochuan Vision.Click to learn more about participating.
Source structure
This article selects Element vue@2 version of the source code for reading. Source code from Github Clone github.com/ElemeFE/ele… .
. ├ ─ ─ build -- -- -- -- -- -- -- -- -- -- -- -- -- -- packing tool configuration file ├ ─ ─ examples -- -- -- -- -- -- -- -- -- -- - ElementUI component sample ├ ─ ─ packages -- -- -- -- -- -- -- -- -- -- - component source ├ ─ ─ the SRC -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - entry documents as well as the method of tool ├ ─ ─ the test -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- unit test └ ─ ─ types -- -- -- -- -- -- -- -- -- -- -- -- -- -- declaration file, used in the typescriptCopy the code
It can be seen that element’s source code structure is relatively simple and clear.
Next, do the dependency installation to get the project up and running.
Yarn // Installation depends on yarn dev/to start the projectCopy the code
Directly lock the message components, source code files in the element/packages/message directory, the test file is located in the element/test/unit/specs/message. Spec. Js
The source code parsing
Why can I call message with this.$message
The only global method calls you can make in Vue using this. XXX are basically method or attribute additions in Vue.prototype.
Element/SRC /index.js [:203]
Off-topic: a few suggestions for the introduction of the header of the file. There are a lot of files introduced here, and there is no sorting, if there is sorting in the code to find more convenient, plus I have obsessive-compulsive disorder. A vscode plug-in, Sort lines, is recommended. The sorted code looks like this. Which one do you prefer
Message is defined in the element/packages/Message/SRC/main js
Message method implementation
element/packages/message/src/main.js
The message skeleton is preserved and the code is simplified as follows:
Distinguish between Message and Message!!
- [8] Current Message instance
- [9] Message instance collection
- [10] Used to generate message IDS
- [12] Message initialization method
- [47] Message: $this.$Message ‘success’/’waring’/’info’/’error’
- [62] Message.close method, mainly used to update the set of Message instances and change the top value of the specified Message instance, and called in the onclose of the Message instance
- [85] The message. closeAll method iterates through the collection of Message instances and calls the close method of Message instances to close all Message instances
[12] Message initialization method
const Message = function(options) {
if (Vue.prototype.$isServer) return; // If it is server-side rendering, it is not executed
options = options || {}; // If options are empty, you can also set the default parameters to achieve this effect
if (typeof options === 'string') { // Convert an argument type passed directly to a string to an object
options = {
message: options
};
}
let userOnClose = options.onClose; // Get "closed callback function"
let id = 'message_' + seed++; // Generates the message instance ID, applied to message. close to find the specified message instance
options.onClose = function() { // Override the onClose method
Message.close(id, userOnClose);
};
instance = new MessageConstructor({ // Create a message instance (note!! At this point, only a vue instance is created, rendering is not complete.
data: options
});
instance.id = id; / / set id
if (isVNode(instance.message)) { // If the argument message is of type VNode, assign it to instance.$slots.default and render it through slot
instance.$slots.default = [instance.message];
instance.message = null;
}
instance.$mount(); // Generate the actual node of the instance
document.body.appendChild(instance.$el); // dom is mounted to body
let verticalOffset = options.offset || 20; // Get the set offset, if not the default value 20. Finally, set the top value of the Message instance
instances.forEach(item= > { // Iterate over existing message instances, increasing the top value based on the height of the elements + spacing (16). You can see that for each additional message, the space between them is 16
verticalOffset += item.$el.offsetHeight + 16;
});
instance.verticalOffset = verticalOffset; // Set the final calculated verticalOffset
instance.visible = true; / / display the message
instance.$el.style.zIndex = PopupManager.nextZIndex(); // Set z-index via global popover management
instances.push(instance); // Add to the collection of message instances
return instance; Return the current message instance
};
Copy the code
[47] Different types of Message methods
['success'.'warning'.'info'.'error'].forEach(type= > { // Iterate over the four types of type
Message[type] = (options) = > { // Add a type method for Message, so it can be called as this.$message.success
if(isObject(options) && ! isVNode(options)) {// If options is passed as object type && not VNode
return Message({ // Return Message of the specified type,. options, type }); }return Message({
type,
message: options
});
};
});
Copy the code
[62] message.close method
Message.close = function(id, userOnClose) {
let len = instances.length; // Get the number of existing message instances
let index = -1; / / index
let removedHeight; // Remove the height of message element
for (let i = 0; i < len; i++) { // Iterate through the collection of message instances
if (id === instances[i].id) { // Find the corresponding message instance by id
removedHeight = instances[i].$el.offsetHeight; // Get the height of the removed message
index = i;
if (typeof userOnClose === 'function') { // If the userOnClose callback is set, the function is called and the message instance is passed
userOnClose(instances[i]);
}
instances.splice(i, 1); // Remove the currently closed instance from the message instance collection
break; }}if (len <= 1 || index === -1 || index > instances.length - 1) return; / / if only one | | instances in the collection does not contain the incoming id | | to shut down the instance is the last one; The subsequent logic to change the top value is not performed
for (let i = index; i < len - 1 ; i++) { // Start with the closed instance and update the top value after that instance. The purpose is to move subsequent messages up to that location after the message is removed. However, the current instance is not destroyed and still exists on the page
let dom = instances[i].$el;
dom.style['top'] =
parseInt(dom.style['top'].10) - removedHeight - 16 + 'px'; }};Copy the code
You can use this example to get a feel for the message.close method
<template>
<el-button :plain="true" @click="open">Open message prompt</el-button>
<el-button :plain="true" @click="openVn">VNode</el-button>
<el-button :plain="true" @click="close">close</el-button>
</template>
<script>
export default {
data() {
return {
message: [].open: (() = > {
let index = 0;
const types = ["info"."success"."warning"."error"];
return () = > {
const msg = this.$message({
message: "this is a message".duration: 0.type: types[index++ % 4]});this.message.push(msg); }; }) ()}; },methods: {
// open() {
// this.$message(" this is a message ");
// },
openVn() {
const h = this.$createElement;
this.$message({
message: h('p'.null, [
h('span'.null.'Content can be'),
h('i', { style: 'color: teal' }, 'VNode')])}); },close() {
const index = 4
this.message.length > index &&
this.$message.close(
this.message[this.message.length - index].id,
(instance) = > {
console.log("instance: ", instance);
this.message.splice(this.message.length - index, 1); }); ,}}};</script>
Copy the code
Copy this code to Element /examples/docs/ zh-cn /message.md to preview. Click the “Open message prompt” button several times to generate multiple messages, and then click the “Close” button to observe that messages can be sent without being destroyed, but the position of subsequent messages has been changed.
[85] message. closeAll method
Message.closeAll = function() {
for (let i = instances.length - 1; i >= 0; i--) { // Iterate over the collection of message instances, calling the instance's close method. Note not message.close. The method to define the element/packages/message/SRC/main vueinstances[i].close(); }};Copy the code
Message component implementation
element/packages/message/src/main.vue
The same simplification is shown in the picture:
- [1-25] Component template
- [28] Type corresponds to icon mapping map
- [36] Component Data
- [55] typeClass is the type calculated according to type, mainly defining different icon displays according to type type and mapping relationship [28]. This also reflects the beauty of [28] defining mapping relations. For example, if you want to change the type of icon later, you can modify the mapping relations.
- [60] Calculate the top value according to verticalOffset
- [68] Listen for closed changes, if true, set this.visible = false; V-show is false and the element is hidden. Call transition after-leave, i.e. [76] handleAfterLeave, to perform component destruction and remove the DOM node.
- [76] Perform component destruction and remove the DOM node
- [81] Set this.close to true, => 68 => 76. This method simply closes the current MESSAGE API
- [88] Clear timer
- [92] Start the timer. When duration > 0 is set, the timer is closed
- [101] Press esc to close
- [109] Mounted Enables the timer to listen to keyDown events
- [113] beforeDestroy Cancels listening for keyDown events
The test case
The Version of Element VU2 uses Karma.
Test file is located in the message: element/test/unit/specs/message. Spec. Js
afterEach
AfterEach: this method is executed afterEach test case is executed.
- [6] Find the element whose class name is el-message.
- [7] If the node does not exist, return
- [8] If there is a parent node of the node, remove the node from the parent node
- [11] If the node has
__vue__
Property to call the vue instancedestroy
hook
el.__vue__
Here’s a trick. It may not be common to write this, manipulating vue instances through real nodes. By the way, explain why a Vue instance can be accessed through the __vue__ attribute of a real node.
In vue @ 2 source SRC/core/instance/lifecycle. In js:
Prototype.$mount -> mountComponent -> new Watcher -> vm._update -> vm.$el.__vue__ = vm
This is why the VM can be accessed through the __vue__ of the real DOM node. Those interested can read the vue2 source code.
The rest of the test cases will not be expanded here.
conclusion
Examples of collections
Instances store the collection of Message instances, and mark messages with self-increasing ids to facilitate searching in subsequent operations.
Closure application
Use closures to cache ids when overriding the onClose method. At the same time, we can rewrite the options.onClose method to associate Message with the component, so that when onClose is called in subsequent component instance methods, the instance can be accurately found in the Instances.
The use of vm. $mount ()
Normally, this method is called with an EL to mount. But there is no passing in here, just to generate the actual DOM node to vm.$el, which will be added manually to document.body later
PopupManager
PopupManager global popup management level, PopupManager will not be expanded here
Event listening and destruction
My personal habit might be to write code like this:
mounted() { this.startTimer(); document.addEventListener('keydown', this.keydown); this.$on("hook:beforeDestroy",()=>{ document.removeEventListener('keydown', this.keydown); })},Copy the code
In the Element VUe3 version, this is changed to useEventListener:
__vue__
The VM instance can be accessed through the VUE of the real node. This is applied in test cases. Of course, it can be used for reference in our actual development.