小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
介绍
相信无数的开发者天天都在使用vue,其中v-show和v-if绑定语法几乎每个页面都会多少用到,但是考虑过他们是如何实现的吗?本期的案例就是制作一个miniVue只讲解v-show与v-if的实现,因为有点击所以我们把click事件顺便也简单实现下。
如图所示,我们做的找个案例是要,点击clickBox后,ifBoX与showBox会交替显隐,我们这就开始咯~
正文
1.基本结构
<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>
复制代码
基本结构与vue书写语法基本一样,只是为了区分,我们把v-if与v-show换成m-if和m-show。
所有的实现逻辑将在MS.js完成,接下来,就要实现他。
2.逻辑结构
我们可以看到,我们在上面的html中,使用的el,data,methods,watch这些都是与vue一模一样的。所以我们先创建MS类,做一个简单的逻辑结构去收集他们。
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();
this.$eventPool = new Map();
this.init();
}
init() {
this.initData();
this.initDom(this.$el);
this.initRender();
this.bindEvent();
}
initData() {}
initDom(el) {}
initRender() {}
renderNode(key, value) {}
bindEvent() {}
}
}
export default MS;
复制代码
- $el:记录主容器
- $data:收集了传入的数据
- $methods:收集了传入的方法
- $watch:收集了传入要监听的内容
- $dataPool:数据池,主要存放数据和对应节点
- $eventPool:事件池,主要存放数据和对应事件
我们在初始化时要先要初始化这些数据然后绑定dom和事件,然后渲染出来。
3.初始化数据
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);
}
})
}
}
}
复制代码
这里了解过vue2源码的同学应该很清楚,我们先遍历绑定下数据,用Object.defineProperty跟其做数据代理,设置get和set方法。
现在期望的是:
- get:返回当前key要查的值
- set:设置新值,当然先记录一下旧值,因为我们后面会在$watch做监听把新值和旧值都要传过去,当然还要把当前key的数据改成新值,然后将会用renderNode方法做渲染。
4.初始化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)
})
}
复制代码
这里我们从父容器拿到下面的子节点,遍历他这些字节点,如果出现如m-show,m-if,@click这些绑定语法,就要收集下来,存储到对应的数据池 eventPool中。这样一旦触发当中的变量或者事件,他们的节点也会捕获到。
5.绑定事件
bindEvent() {
for (const [dom, obj] of this.$eventPool) {
dom.addEventListener(obj.type, obj.event.bind(this), false)
}
}
复制代码
刚刚我们在事件池$eventPool收集的事件,我们还要根据其dom一一的去绑定。
6.渲染DOM
我们先将怎么初始化渲染,代码如下:
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;
}
}
}
复制代码
也刚刚我们在数据池$dataPool收集的数据,要根据其dom一一的去绑定。这里我们仅仅处理show和if两种类型。
-
show:很简单,我们存一下原始的style.display的值,他的显隐就是不断改变style.display的值是不是none罢了。
-
if:或许,我们咋一开始想不到,因为他是用document.createComment创建并保存一个虚的节点,然后根据他的显隐来是否替换原来的dom。
当然,我们每次数据变动还要重新渲染要改变数据的节点。
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;
}
}
}
}
复制代码
重新渲染,跟上面初始化渲染差不多,只是不需要再记录和创建了,只做更改和替换。
结语
写到这里我们就实现了一个自己的v-if和v-show语法,也可以监听到他的改变。本质还是依赖收集,根据变量的改变而改变从而渲染dom,show用style.display去实现,而if用document.createComment方法生成节点去替换原来的。当然你也可以,再此基础上继续扩展,完成跟vue一样的有趣能力。