天天用v-show和v-if还不知道怎么实现?今天就让你极速玩转
介绍
相信无数的开发者天天都在使用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这些绑定语法,就要收集下来,存储到对应的数据池dataPool和事件池dataPool和事件池dataPool和事件池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一样的有趣能力。
作者:jsmask
链接:https://juejin.cn/post/7017650102273572900