Maison  >  Article  >  interface Web  >  Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

青灯夜游
青灯夜游avant
2022-03-03 19:41:263306parcourir

Comment implémenter la liaison bidirectionnelle dans vue ? Cet article vous apprendra étape par étape comment rédiger une reliure bidirectionnelle vue, afin que tout le monde puisse mieux comprendre la direction logique de la reliure bidirectionnelle. J'espère que cela sera utile à tout le monde !

Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

Cet article est principalement un processus d'écriture des pièges et de remplissage des pièges, permettant au public de mieux comprendre comment mettre en œuvre la liaison bidirectionnelle et la direction logique de la liaison bidirectionnelle. À ce stade, nous mettrons en œuvre une liaison bidirectionnelle. -liaison étape par étape. Il s'agit d'un article de didacticiel de classe. Tant que vous suivez l'article et observez attentivement chaque classe, ce n'est pas difficile à mettre en œuvre.

Démarrer

Démarrer Mage ? Amenez un chien ! !

Cela ne semble pas correct

Commencez avec une image

Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

Vous pouvez voir sur l'image que la nouvelle Vue() est divisée en deux étapesnew Vue()分为了两步走

  • 代理监听所有数据,并与Dep进行关联,通过Dep通知订阅者进行视图更新。【相关推荐:vuejs视频教程

  • 解析所有模板,并将模板中所用到的数据进行订阅,并绑定一个更新函数,数据发生改变时Dep通知订阅者执行更新函数。

接下里就是分析如何去实现,并且都需要写什么,先看一段vue的基础代码,我们从头开始分析

<div id="app">
  <input v-model="message" />
  <p>{{message}}</p>
</div>
let app = new Vue({
    el:"#app",
    data:{
      message:"测试这是一个内容"
    }
})

从上面代码我们可以看到new Vue的操作,里面携带了eldata属性,这算是最基础的属性,而在html代码中我们知道<div id="app">是vue渲染的模板根节点,所以vue要渲染页面就要去实现一个模板解析的方法<code>Compile类,解析方法中还需要去处理{{ }}v-model两个指令,除了解析模板之后我们还需要去实现数据代理也就是实现Observer

实现 Vue 类

如下代码所示,这就写完了Vue类,够简单吧,如果对class关键字不熟悉的,建议先去学习一下,从下面我们可能看到,这里实例化了两个类,一个是代理数据的类,一个是解析模板的类。

class Vue {
  constructor(options) {
    // 代理数据
    new Observer(options.data)
    // 绑定数据
    this.data = options.data
    // 解析模板
    new Compile(options.el, this)
  }
}

接着往下我们先写一个Compile类用于解析模板,我们再来分析一波,解析模板要做什么事

  • 我们要解析模板不可能直接对dom继续操作,所以我们要创建一个文档片段(虚拟dom),然后将模板DOM节点复制一份到虚拟DOM节点中,对虚拟DOM节点解析完成之后,再将虚拟DOM节点替换掉原来的DOM节点

  • 虚拟节点复制出来之后,我们要遍历整个节点树进行解析,解析过程中会对DOM的atrr属性进行遍历找到Vue相关的指令,除此之外还要对 textContent节点内容进行解析,判断是否存在双花括号

  • 将解析出来所用到的属性进行一个订阅

实现模板解析 Compile 类

下面我们将逐步实现

  • 构建Compile类,先把静态节点和Vue实例获取出来,再定义一个虚拟dom的属性用来存储虚拟dom

class Compile {
  constructor(el, vm) {
    // 获取静态节点
    this.el = document.querySelector(el);
    // vue实例
    this.vm = vm 
    // 虚拟dom
    this.fragment = null 
    // 初始化方法
    this.init()
  }
}
  • 实现初始化方法init(),该方法主要是用于创建虚拟dom和调用解析模板的方法,解析完成之后再将DOM节点替换到页面中

class Compile { 
  //...省略其他代码

  init() {
    // 创建一个新的空白的文档片段(虚拟dom)
    this.fragment = document.createDocumentFragment()
  	// 遍历所有子节点加入到虚拟dom中
    Array.from(this.el.children).forEach(child => {
      this.fragment.appendChild(child)
    })
    // 解析模板
    this.parseTemplate(this.fragment)
    // 解析完成添加到页面
    this.el.appendChild(this.fragment);
  }
}
  • 实现解析模板方法parseTemplate,主要是遍历虚拟DOM中的所有子节点并进行解析,根据子节点类型进行不同的处理。

class Compile { 
  //...省略其他代码

  // 解析模板 
  parseTemplate(fragment) {
    // 获取虚拟DOM的子节点
    let childNodes = fragment.childNodes || []
    // 遍历节点
    childNodes.forEach((node) => {
      // 匹配大括号正则表达式 
      var reg = /\{\{(.*)\}\}/;
      // 获取节点文本
      var text = node.textContent;
      if (this.isElementNode(node)) { // 判断是否是html元素
        // 解析html元素
        this.parseHtml(node)
      } else if (this.isTextNode(node) && reg.test(text)) { //判断是否文本节点并带有双花括号
        // 解析文本
        this.parseText(node, reg.exec(text)[1])
      }

      // 递归解析,如果还有子元素则继续解析
      if (node.childNodes && node.childNodes.length != 0) {
        this.parseTemplate(node)
      }
    });
  }
}
  • 根据上面的代码我们得出需要实现两个简单的判断,也就是判断是否是html元素和文字元素,这里通过获取nodeType的值来进行区分,不了解的可以直接看一下 传送门:Node.nodeType,这里还扩展了一个isVueTag方法,用于后面的代码中使用

class Compile { 
  //...省略其他代码

	// 判断是否携带 v-
  isVueTag(attrName) {
    return attrName.indexOf("v-") == 0
  }
  // 判断是否是html元素
  isElementNode(node) {
    return node.nodeType == 1;
  }
  // 判断是否是文字元素
  isTextNode(node) {
    return node.nodeType == 3;
  }
}
  • 实现parseHtml

    • L'agent écoute toutes les données et s'associe à Dep, et informe les abonnés des mises à jour de vue via Dep. [Recommandations associées : tutoriel vidéo vuejs🎜]🎜
    • 🎜Analysez tous les modèles, abonnez-vous aux données utilisées dans le modèle et liez une fonction de mise à jour. Lorsque les données changent, Dep informe l'abonné d'exécuter la fonction de mise à jour. 🎜
    🎜L'étape suivante consiste à analyser comment l'implémenter et ce qui doit être écrit. Examinons d'abord un morceau de code de base de vue. Analysons-le depuis le début🎜
    class Compile {
      //...省略其他代码
    
      // 解析html
      parseHtml(node) {
        // 获取元素属性集合
        let nodeAttrs = node.attributes || []
        // 元素属性集合不是数组,所以这里要转成数组之后再遍历
        Array.from(nodeAttrs).forEach((attr) => {
          // 获取属性名称
          let arrtName = attr.name;
          // 判断名称是否带有 v- 
          if (this.isVueTag(arrtName)) {
            // 获取属性值
            let exp = attr.value;
            //切割 v- 之后的字符串
            let tag = arrtName.substring(2);
            if (tag == "model") {
              // v-model 指令处理方法
              this.modelCommand(node, exp, tag)
            }
          }
        });
      }
    }
    class Compile {
    	//...省略其他代码
      
       // 处理model指令
      modelCommand(node, exp) {
        // 获取数据
        let val = this.vm.data[exp]
        // 解析时绑定数据
        node.value = val || ""
    
        // 监听input事件
        node.addEventListener("input", (event) => {
          let newVlaue = event.target.value;
          if (val != newVlaue) {
            // 更新data数据
            this.vm.data[exp] = newVlaue
            // 更新闭包数据,避免双向绑定失效
            val = newVlaue
          }
        })
      }
    }
    🎜À partir de ce qui précède. code que nous pouvons voir Le fonctionnement du nouveau Vue porte les attributs el et data, qui sont les attributs les plus basiques que nous avons dans le code html. sachez que est le nœud racine du modèle rendu par vue, donc pour que vue restitue la page, il doit implémenter une méthode d'analyse de modèle Compile, et la méthode d'analyse doit également Pour traiter les deux instructions <code>{{ }} et v-model, en plus d'analyser le modèle, nous avons également besoin pour implémenter l'agent de données, qui doit implémenter la classe Observer 🎜🎜Implement Vue class🎜🎜Comme le montre le code suivant, cela complète la Vue code>. C'est assez simple. Si vous ne connaissez pas le mot-clé <code>class. Si vous le connaissez, il est recommandé de l'apprendre d'abord. Ce qui suit montre qu'il existe deux classes. instanciée ici, l'une est la classe de données proxy et l'autre est la classe de modèle d'analyse. 🎜
    class Compile {
    	//...省略其他代码
      
      //解析文本
      parseText(node, exp) {
        let val = this.vm.data[exp]
        // 解析更新文本
        node.textContent = val || ""
      }
    }
    🎜 Ensuite, écrivons une classe Compile pour analyser le modèle. Analysons-le à nouveau et voyons ce qui doit être fait pour analyser le modèle🎜
    • 🎜Si nous voulons analyser le modèle, il est impossible de continuer à opérer directement sur le DOM, nous devons donc créer un fragment de document (DOM virtuel), puis copier le nœud DOM du modèle sur le nœud DOM virtuel. , et terminez l'analyse du nœud DOM virtuel. Après cela, remplacez le nœud DOM d'origine par le nœud DOM virtuel🎜
    • 🎜Une fois le nœud virtuel copié, nous devons parcourir toute l'arborescence des nœuds pour l'analyse. . Pendant le processus d'analyse, l'attribut atrr du DOM sera parcouru pour trouver les instructions liées à Vue, en plus d'analyser le contenu du nœud textContent pour déterminer s'il y a des doubles accolades🎜
    • 🎜abonnez-vous aux attributs analysés🎜
    🎜Implémentez la classe Compile d'analyse du modèle🎜🎜Nous l'implémenterons étape par étape ci-dessous🎜
    • 🎜Construisez la classe Compile, obtenez d'abord le nœud statique et l'instance Vue, puis définissez un attribut de dom virtuel pour stocker le dom virtuel🎜
    // 监听者
    class Observer {
      constructor(data) {
        this.observe(data)
      }
      // 递归方法
      observe(data) {
        //判断数据如果为空并且不是object类型则返回空字符串
        if (!data || typeof data != "object") {
          return ""
        } else {
          //遍历data进行数据代理
          Object.keys(data).forEach(key => {
            this.defineReactive(data, key, data[key])
          })
        }
      }
    
      // 代理方法
      defineReactive(data, key, val) {
        // 递归子属性
        this.observe(data[key])
        Object.defineProperty(data, key, {
          configurable: true,  //可配置的属性
          enumerable: true, //可遍历的属性
          get() {
            return val
          },
          set(newValue) {
            val = newValue
          }
        })
      }
    }
      🎜Implémentez la méthode d'initialisation init(), qui est principalement utilisée pour créer un dom virtuel et appeler la méthode du modèle d'analyse une fois l'analyse terminée. , le nœud DOM est remplacé dans la page🎜
  • class Vue {
      constructor(options) {
        // 代理数据
        new Observer(options.data)
        console.log(options.data)
        // 绑定数据
        this.data = options.data
        // 解析模板
        new Compile(options.el, this)
      }
    }
    • 🎜Implémente la méthode du modèle d'analyse parseTemplate, qui parcourt principalement tous les nœuds enfants du DOM virtuel et les analyse. Différents traitements sont effectués en fonction du type de nœud enfant. 🎜
    class Dep {
      constructor() {
        // 记录订阅者
        this.subList = []
      }
      // 添加订阅者
      addSub(sub) {
        // 先判断是否存在,防止重复添加订阅者
        if (this.subList.indexOf(sub) == -1) {
          this.subList.push(sub)
        }
      }
      // 通知订阅者
      notify() {
        this.subList.forEach(item => {
          item.update() //订阅者执行更新,这里的item就是一个订阅者,update就是订阅者提供的方法
        })
      }
    }
    // Dep全局属性,用来临时存储订阅者
    Dep.target = null
    // 订阅者
    class Watcher {
      // vm:vue实例本身
      // exp:代理数据的属性名称
      // cb:更新时需要做的事情
      constructor(vm, exp, cb) {
        this.vm = vm
        this.exp = exp
        this.cb = cb
        this.putIn()
      }
      update() {
        // 调用cb方法体,改变this指向并传入最新的数据作为参数
        this.cb.call(this.vm, this.vm.data[this.exp])
      }
      putIn() {
        // 把订阅者本身绑定到Dep的target全局属性上
        Dep.target = this
        // 调用获取数据的方法将订阅者加入到管理器中
        let val = this.vm.data[this.exp]
        // 清空全局属性
        Dep.target = null
      }
    }
    • 🎜 Implémentez la méthode parseHtml L'analyse du code html traverse principalement. l'attr sur l'élément html Propriétés 🎜.
    class Compile {
      //...省略其他代码
    
      // 解析html
      parseHtml(node) {
        // 获取元素属性集合
        let nodeAttrs = node.attributes || []
        // 元素属性集合不是数组,所以这里要转成数组之后再遍历
        Array.from(nodeAttrs).forEach((attr) => {
          // 获取属性名称
          let arrtName = attr.name;
          // 判断名称是否带有 v- 
          if (this.isVueTag(arrtName)) {
            // 获取属性值
            let exp = attr.value;
            //切割 v- 之后的字符串
            let tag = arrtName.substring(2);
            if (tag == "model") {
              // v-model 指令处理方法
              this.modelCommand(node, exp, tag)
            }
          }
        });
      }
    }
    • 实现modelCommand方法,在模板解析阶段来说,我们只要把 vue实例中data的值绑定到元素上,并实现监听input方法更新数据即可。

    class Compile {
    	//...省略其他代码
      
       // 处理model指令
      modelCommand(node, exp) {
        // 获取数据
        let val = this.vm.data[exp]
        // 解析时绑定数据
        node.value = val || ""
    
        // 监听input事件
        node.addEventListener("input", (event) => {
          let newVlaue = event.target.value;
          if (val != newVlaue) {
            // 更新data数据
            this.vm.data[exp] = newVlaue
            // 更新闭包数据,避免双向绑定失效
            val = newVlaue
          }
        })
      }
    }
    • 处理Text元素就相对简单了,主要是将元素中的textContent内容替换成数据即可

    class Compile {
    	//...省略其他代码
      
      //解析文本
      parseText(node, exp) {
        let val = this.vm.data[exp]
        // 解析更新文本
        node.textContent = val || ""
      }
    }

    至此已经完成了Compile类的初步编写,测试结果如下,已经能够正常解析模板

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

    下面就是我们目前所实现的流程图部分

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

    坑点一:

    • 在第6点modelCommand方法中并没有实现双向绑定,只是单向绑定,后续要双向绑定时还需要继续处理

    坑点二:

    • 第7点parseText方法上面的代码中并没有去订阅数据的改变,所以这里只会在模板解析时绑定一次数据

    实现数据代理 Observer 类

    这里主要是用于代理data中的所有数据,这里会用到一个Object.defineProperty方法,如果不了解这个方法的先去看一下文档传送门:

    文档:

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

    Observer类主要是一个递归遍历所有data中的属性然后进行数据代理的的一个方法

    defineReactive中传入三个参数data, key, val

    datakey都是Object.defineProperty的参数,而val将其作为一个闭包变量供Object.defineProperty使用

    // 监听者
    class Observer {
      constructor(data) {
        this.observe(data)
      }
      // 递归方法
      observe(data) {
        //判断数据如果为空并且不是object类型则返回空字符串
        if (!data || typeof data != "object") {
          return ""
        } else {
          //遍历data进行数据代理
          Object.keys(data).forEach(key => {
            this.defineReactive(data, key, data[key])
          })
        }
      }
    
      // 代理方法
      defineReactive(data, key, val) {
        // 递归子属性
        this.observe(data[key])
        Object.defineProperty(data, key, {
          configurable: true,  //可配置的属性
          enumerable: true, //可遍历的属性
          get() {
            return val
          },
          set(newValue) {
            val = newValue
          }
        })
      }
    }

    下面我们来测试一下是否成功实现了数据代理,在Vue的构造函数输出一下数据

    class Vue {
      constructor(options) {
        // 代理数据
        new Observer(options.data)
        console.log(options.data)
        // 绑定数据
        this.data = options.data
        // 解析模板
        new Compile(options.el, this)
      }
    }

    结果如下,我们可以看出已经实现了数据代理。

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

    对应的流程图如下所示

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

    坑点三:

    • 这里虽然实现了数据代理,但是按照图上来说,还需要引入管理器,在数据发生变化时通知管理器数据发生了变化,然后管理器再通知订阅者更新视图,这个会在后续的填坑过程过讲到。

    实现管理器 Dep 类

    上面我们已经实现了模板解析到初始化视图,还有数据代理。而下面要实现的Dep类主要是用于管理订阅者和通知订阅者,这里会用一个数组来记录每个订阅者,而类中也会给出一个notify方法去调用订阅者的update方法,实现通知订阅者更新功能。这里还定义了一个target属性用来存储临时的订阅者,用于加入管理器时使用。

    class Dep {
      constructor() {
        // 记录订阅者
        this.subList = []
      }
      // 添加订阅者
      addSub(sub) {
        // 先判断是否存在,防止重复添加订阅者
        if (this.subList.indexOf(sub) == -1) {
          this.subList.push(sub)
        }
      }
      // 通知订阅者
      notify() {
        this.subList.forEach(item => {
          item.update() //订阅者执行更新,这里的item就是一个订阅者,update就是订阅者提供的方法
        })
      }
    }
    // Dep全局属性,用来临时存储订阅者
    Dep.target = null

    管理器实现完成之后我们也就实现了流程图中的以下部分。要注意下面几点

    • Observer通知Dep主要是通过调用notify方法
    • Dep通知Watcher主要是是调用了Watcher类中的update方法

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue


    实现订阅者 Watcher 类

    订阅者代码相对少,但是理解起来还是有点难度的,在Watcher类中实现了两个方法,一个是update更新视图方法,一个putIn方法(我看了好几篇文章都是定义成 get 方法,可能是因为我理解的不够好吧)。

    • update:主要是调用传入的cb方法体,用于更新页面数据
    • putIn:主要是用来手动加入到Dep管理器中。
    // 订阅者
    class Watcher {
      // vm:vue实例本身
      // exp:代理数据的属性名称
      // cb:更新时需要做的事情
      constructor(vm, exp, cb) {
        this.vm = vm
        this.exp = exp
        this.cb = cb
        this.putIn()
      }
      update() {
        // 调用cb方法体,改变this指向并传入最新的数据作为参数
        this.cb.call(this.vm, this.vm.data[this.exp])
      }
      putIn() {
        // 把订阅者本身绑定到Dep的target全局属性上
        Dep.target = this
        // 调用获取数据的方法将订阅者加入到管理器中
        let val = this.vm.data[this.exp]
        // 清空全局属性
        Dep.target = null
      }
    }

    坑点四:

    • Watcher类中的putIn方法再构造函数调用后并没有加入到管理器中,而是将订阅者本身绑定到target全局属性上而已

    埋坑

    通过上面的代码我们已经完成了每一个类的构建,如下图所示,但是还是有几个流程是有问题的,也就是上面的坑点。所以下面要填坑

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

    埋坑 1 和 2

    完成坑点一和坑点二,在modelCommandparseText方法中增加实例化订阅者代码,并自定义要更新时执行的方法,其实就是更新时去更新页面中的值即可

    modelCommand(node, exp) {
      
      // ...省略其他代码
      
      // 实例化订阅者,更新时直接更新node的值
      new Watcher(this.vm, exp, (value) => {
        node.value = value
      })
    }
    
    
    parseText(node, exp) {
      
      //  ...省略其他代码
      
      // 实例化订阅者,更新时直接更新文本内容
      new Watcher(this.vm, exp, (value) => {
        node.textContent = value
      })
    }

    埋坑 3

    完成坑点三,主要是为了引入管理器,通知管理器发生改变,主要是在Object.defineProperty set方法中调用dep.notify()方法

    // 监听方法
    defineReactive(data, key, val) {
      // 实例化管理器--------------增加这一行
      let dep = new Dep()
      
      // ...省略其他代码
      
        set(newValue) {
          val = newValue
          // 通知管理器改变--------------增加这一行
          dep.notify()
        }
    
    }

    埋坑 4

    完成坑点四,主要四将订阅者加入到管理器中

    defineReactive(data, key, val) {
      // ...省略其他代码
        get() {
          // 将订阅者加入到管理器中--------------增加这一段
          if (Dep.target) {
            dep.addSub(Dep.target)
          }
          return val
        },
      // ...省略其他代码
    }

    完成了坑点四可能就会有靓仔疑惑了,这里是怎么加入的呢Dep.target又是什么呢,我们不妨从头看看代码并结合下面这张图

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

    至此我们已经实现了一个简单的双向绑定,下面测试一下

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

    完结撒花

    总结

    本文解释的并不多,所以才是类教程文章,如果读者有不懂的地方可以在评论去留言讨论

    Vous apprendre étape par étape comment implémenter une liaison bidirectionnelle vue

    (学习视频分享:vuejs教程web前端

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer
Article précédent:Que sont les composants ? Vous amène à une compréhension approfondie des composants Vue.js !Article suivant:Que sont les composants ? Vous amène à une compréhension approfondie des composants Vue.js !

Articles Liés

Voir plus