Small knowledge, big challenge! This paper is participating in theEssentials for programmers”Creative activities
This article also participated in the “Digitalstar Project” to win a creative gift package and creative incentive money
introduce
Countless developers use VUE every day, with v-show and V-if binding syntax used in almost every page, but have you ever considered how they are implemented? This issue’s case is to make a miniVue only to explain the implementation of V-show and V-if, because there is a click so we put the click event incidentally also simple implementation.
As you can see in this example, if you click on a clickBox, ifBoX and showBox will appear alternately, so here we go
The body of the
1. Basic structure
<div id="app">
<div class="box-wrapper">
<div class="box style-click" @click="changeBox">Click Box</div>
<div class="box style-if" m-if="ifBox">IF Box</div>
<div class="box style-show" m-show="showBox" style="display: flex;">Show Box</div>
</div>
</div>
<script type="module">
import MS from "./js/MS";
let ms = new MS({
el: "#app".data() {
return {
showBox: true.ifBox: false}; },methods: {
changeBox() {
this.showBox = !this.showBox;
this.ifBox = !this.ifBox; }},watch: {
showBox(newValue, oldValue) {
console.log(`showBox:${oldValue}->${newValue}`);
},
ifBox(newValue, oldValue) {
console.log(`ifBox:${oldValue}->${newValue}`); }}});</script>
Copy the code
The basic structure is basically the same as vUE writing syntax, just to distinguish between v-if and V-show, we replaced m-if and m-show.
All the implementation logic will be done in Ms. Js. Next, implement it.
2. Logical structure
As you can see, the EL, data, methods, watch that we use in the HTML above are exactly the same as vue. So let’s first create MS classes and make a simple logical structure to collect them.
class MS {
constructor(options) {
this.$el = document.querySelector(options.el);
this.$data = options.data && options.data();
this.$methods = options.methods;
this.$watch = options.watch;
this.$dataPool = new Map(a);this.$eventPool = new Map(a);this.init();
}
init() {
this.initData();
this.initDom(this.$el);
this.initRender();
this.bindEvent();
}
initData() {}
initDom(el) {}
initRender() {}
renderNode(key, value) {}
bindEvent(){}}}export default MS;
Copy the code
- $el: Records the primary container
- $data: Collected incoming data
- $methods: Collects incoming methods
- $watch: collects incoming content to listen on
- $dataPool: stores data and corresponding nodes
- $eventPool: an eventPool that stores data and events
So what we do when we initialize is we initialize the data and then we bind the DOM and the event and then we render it.
3. Initialize data
initData() {
const {$data,$watch} = this;
for (const key in $data) {
if (Object.hasOwnProperty.call($data, key)) {
Object.defineProperty(this, key, {
get() {
return $data[key]
},
set(value) {
let oldValue = $data[key];
$data[key] = value;
this.renderNode(key, value); $watch[key]&&$watch[key](value,oldValue); }})}}}Copy the code
We first iterate over the binding data, proxy it with Object.defineProperty, and set the get and set methods.
Now the expectation is:
- Get: Returns the value of the current key
- Set: set the new value, record the old value, because we will be listening in $watch to pass both the new value and the old value, and of course change the current key to the new value, and then render with renderNode.
4. Initialize the DOM
initDom(el) {
let _childNodes = el.childNodes;
if(! _childNodes.length)return false;
_childNodes.forEach(node= > {
if (node.nodeType == 1) {
let mShow = node.getAttribute("m-show");
let mIf = node.getAttribute("m-if");
let mClick = node.getAttribute("@click")
if (mShow) {
this.$dataPool.set(node, {
type: "show".key: mShow
})
}
if (mIf) {
this.$dataPool.set(node, {
type: "if".key: mIf
})
}
if (mClick) {
this.$eventPool.set(node, {
type: "click".key: mClick,
event: this.$methods[mClick]
})
}
}
this.initDom(node)
})
}
Copy the code
If there is a binding syntax such as m-show,m-if,@click, we need to collect it and store it in the corresponding dataPool and eventPool dataPool and eventPool eventPool. So that when variables or events are triggered, their nodes will also catch them.
5. Bind events
bindEvent() {
for (const [dom, obj] of this.$eventPool) {
dom.addEventListener(obj.type, obj.event.bind(this), false)}}Copy the code
The events we just collected in the eventPool $eventPool need to be unbound according to their dom.
6. Rendering DOM
We will first initialize the render as follows:
initRender() {
for (const [dom, obj] of this.$dataPool) {
let value = this.$data[obj.key];
switch (obj.type) {
case "show": obj.value = dom.style.display; ! value && (dom.style.display ="none");
break;
case "if":
obj.comment = document.createComment("m-if"); ! value && dom.parentNode.replaceChild(obj.comment, dom)break;
default:
break; }}}Copy the code
The data we collected in the dataPool $dataPool will be unbound according to its DOM. Here we only deal with the show and if types.
-
Show: let’s save the original style.display value, which is always changing the style.display value to none.
-
If: Maybe we didn’t think of it at first, because it creates and saves a virtual node using document.createcomment, and then replaces the original DOM depending on whether it’s explicit or implicit.
Of course, we have to re-render the node that changed the data every time the data changed.
renderNode(key, value) {
for (const [dom, obj] of this.$dataPool) {
if (key == obj.key) {
switch (obj.type) {
case "show": dom.style.display = ! value ?"none" : obj.value;
break;
case "if":
!value ? dom.parentNode.replaceChild(obj.comment, dom) :
obj.comment.parentNode.replaceChild(dom, obj.comment);
break;
default:
break; }}}}Copy the code
Re-render, similar to the initial render above, except that there is no need to record and create, just change and replace.
conclusion
At this point we have implemented our own V-if and V-show syntax and can also listen for its changes. Show uses style.display, and if uses document.createComment to generate nodes to replace the original. You can, of course, expand on this and achieve the same interesting capabilities as VUE.