阅读 138

天天用v-show和v-if还不知道怎么实现?今天就让你极速玩转

介绍

相信无数的开发者天天都在使用vue,其中v-show和v-if绑定语法几乎每个页面都会多少用到,但是考虑过他们是如何实现的吗?本期的案例就是制作一个miniVue只讲解v-show与v-if的实现,因为有点击所以我们把click事件顺便也简单实现下。

VID_20211011_090151.gif

如图所示,我们做的找个案例是要,点击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和事件池dataPooleventPool中。这样一旦触发当中的变量或者事件,他们的节点也会捕获到。

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。

微信截图_20211011112740.png

当然,我们每次数据变动还要重新渲染要改变数据的节点。

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一样的有趣能力。

VID_20211011_113527.gif


作者:jsmask
链接:https://juejin.cn/post/7017650102273572900


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐