ホームページ > ウェブフロントエンド > Vue.js > vue の双方向バインディングを実装する方法を段階的に説明します。

vue の双方向バインディングを実装する方法を段階的に説明します。

青灯夜游
リリース: 2022-03-03 19:41:26
転載
3391 人が閲覧しました

vue はどのように双方向バインディングを実装しますか?この記事では、誰もが双方向バインディングの論理的方向をよりよく理解できるように、vue 双方向バインディングの作成方法を説明します。

vue の双方向バインディングを実装する方法を段階的に説明します。

この記事は、読者が双方向バインディングの実装方法と双方向バインディングの論理的方向性をよりよく理解できるように、主に落とし穴を作成して埋めるプロセスです。 . ここまでは、段階的に双方向バインディングを最初から実装してみましょう。これはクラスのチュートリアルの記事です。記事に従って各クラスを注意深く観察する限り、実装は難しくありません。

Start

魔術師を始めませんか? 犬を連れてください! !

正しくないようです

画像からやり直します

vue の双方向バインディングを実装する方法を段階的に説明します。

画像から確認できますnew Vue ()これは 2 つのステップに分かれています

  • エージェントはすべてのデータを監視し、それを Dep に関連付け、## を通じてビューを更新するようにサブスクライバーに通知します。 #Dep 。 [関連する推奨事項: vuejs ビデオ チュートリアル ]

  • すべてのテンプレートを解析し、テンプレートで使用されるデータをサブスクライブし、更新関数をバインドすると、データが発生するとき

    Dep が変更された場合は、更新機能を実行するようにサブスクライバに通知します。

次のステップは、実装方法と何を記述する必要があるかを分析することです。まず、vue の基本コードの一部を見てみましょう。最初から分析しましょう

<div id="app">
  <input v-model="message" />
  <p>{{message}}</p>
</div>
ログイン後にコピー
let app = new Vue({
    el:"#app",
    data:{
      message:"测试这是一个内容"
    }
})
ログイン後にコピー

上記のコードから、

el 属性と data 属性を持つ new Vue の動作がわかります。これは最も基本的な属性です。 HTML コードでは、

が vue によってレンダリングされるテンプレート ルート ノードであることがわかっているため、ページをレンダリングするには、vue がテンプレート解析メソッド Compile# を実装する必要があります。 ## クラス、解析メソッドも必要です。 2 つの命令 {{ }}v-model を処理するには、テンプレートの解析に加えて、データを実装する必要もありますObserver クラス

Vue クラスの実装

#次のコードに示すように、これで が完了します。 Vue

クラスです。非常にシンプルです。

class に興味がある場合は、キーワードに慣れていない場合は、最初にそれらを学習することをお勧めします。以下から、2 つのクラスが次のとおりであることがわかります。ここでインスタンス化されるもののうち、1 つはプロキシ データ クラス、もう 1 つは解析テンプレート クラスです。

class Vue {
  constructor(options) {
    // 代理数据
    new Observer(options.data)
    // 绑定数据
    this.data = options.data
    // 解析模板
    new Compile(options.el, this)
  }
}
ログイン後にコピー
次に、テンプレートを解析するための Compile

クラスを作成しましょう。もう一度分析しましょう。テンプレートを解析するにはどうすればよいですか?

#テンプレートを解析したい場合、DOM上で直接操作を続けることは不可能なので、ドキュメントフラグメント(仮想DOM)を作成し、テンプレートDOMノードを仮想DOMノードにコピーする必要があります。 DOM ノードは、元の DOM ノードを置き換えます
  • #仮想ノードがコピーされた後、解析のためにノード ツリー全体を走査する必要があります。解析プロセス中、atrr DOM の属性は、

    textContent
  • ノードのコンテンツを解析して二重中括弧があるかどうかを判断することに加えて、Vue 関連のコマンドを見つけるために走査されます。解析された属性
  • テンプレート解析の実装コンパイル クラス

  • 以下では、段階的に実装していきます。
  • ##コンパイル#を構築します。 ## クラスでは、最初に静的ノードと 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 要素であるかテキスト要素であるかという 2 つの単純な判断を実装する必要があると結論付けられます。ここでは ## を取得します #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

    メソッドを実装します。HTML コードの解析主に、HTML 要素の attr 属性のトラバースが含まれます。

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类的初步编写,测试结果如下,已经能够正常解析模板

vue の双方向バインディングを実装する方法を段階的に説明します。

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

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)
  }
}
ログイン後にコピー

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

vue の双方向バインディングを実装する方法を段階的に説明します。

对应的流程图如下所示

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方法

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全局属性上而已

埋坑

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

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又是什么呢,我们不妨从头看看代码并结合下面这张图

vue の双方向バインディングを実装する方法を段階的に説明します。

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

vue の双方向バインディングを実装する方法を段階的に説明します。

完结撒花

总结

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

vue の双方向バインディングを実装する方法を段階的に説明します。

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

以上がvue の双方向バインディングを実装する方法を段階的に説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
関連するチュートリアル
人気のおすすめ
最新のコース
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート