blog

VueのMVVMレスポンシブ原則を実現するための300行のコード

はじめに\n\nコマンドパーサーCompileの実装は以下のように行います。\nコマンドパーサーCompileの実装\nオブザーバーの実装\nウォッチャーの実装\nを実装することで、VueのMVVMレ...

Oct 30, 2020 · 9 min. read
シェア

序文

合格

  • 命令パーサーの実装 コンパイル
  • データリスナーの実装 Observer
  • ウォッチャーの実装

Vue全体でMVVMのレスポンシブ原則を実装するために

この記事では、命令パーサーCompileの実装について説明します。

関数の実装は非常に完璧ではありません、あなたが興味を持っている場合は、自分自身を追加することができます、このコードは、主にプロセスの全体の応答性の原則を完了するために、オブザーバを理解することです、コンパイル、ウォッチャーは、何に使用され、どのようにこれらの3つを渡すには、全体のMVVMアーキテクチャへのブリッジを構築するように、Vueの応答性の原則を達成するために。

テストとして簡単なhtmlを作成します。

<div id="app">
 <div>{{person.name}} -- {{person.age}}</div>
 <div>{{person.fav}}</div>
 <ul>
 <li>1</li>
 <li>2</li>
 <li>3</li>
 </ul>
 <div v-html="htmlStr" v-bind:style="{backgroundColor:red}"></div>
 <div v-text="person.fav"></div>
 <!-- <input type="text" v-bind:value='msg'> -->
 <input type="text" v-model='msg'>
 <div>{{msg}}</div>
 <h2 v-text="tip" v-on:click="clickMe"></h2>
</div>

以下のスクリプトタグで、Vueと同じようにVueオブジェクトを作成します。

const Foo = new MVue({
    'root': '#app',
    'data': {
        'bar': {
            'name': 'GHkmmm',
            'c': '21',
            'b': ' '
        },
        'foo': 'Vueの双方向バインディング原則の理解',
        'a': ' ',
        'Obj': 'hello\x20world!'
    },
    'methods': {
        'Bar'() {
            console.log('click');
        }
    }
});

MVue クラスの作成

受信パラメーターの受信に使用

class MVue{
 constructor(options){
 this.$options = options;
 this.$el = options.el;
 this.$data = options.data;

 if(this.$el){
 // 命令パーサの実装
 new Compile(this.$el, this);
 }
 }
}

命令パーサーの実装 コンパイル

class Compile{
 constructor(el, vm){
 this.el = this.isElementNode(el) ? el : document.querySelector(el);
 this.vm = vm;
 /*
 	ページの内容をコンパイルする場合、オブジェクトを繰り返し変更する必要があるため、各オブジェクトを変更することはできません。
 その代わりに、ドキュメントフラグメントを使用して変更を統一し、フラグメントを使用してページ全体を更新します。
 */
 // 1.ドキュメントフラグメントオブジェクトをメモリに取得し、ページのリフローと再描画を削減します。
 const fragment = this.nodeFragment(this.el);
 // 2.コンパイルテンプレート
 this.compile(fragment);
 // 3.ルート要素に子要素を追加
 this.el.append(fragment);
 }
}

ドキュメント・フラグメントの取得

子ノードを繰り返し処理し、ドキュメント・フラグメント・オブジェクトに追加します。

nodeFragment(node){
 // ドキュメント断片化オブジェクトの作成
 const f = document.createDocumentFragment();
 let firstChild;
 // <div id="app">の下の子ノード
 の子ノードです。.firstChild){
 firstChild = node.firstChild;
 // トラバースされたノードをドキュメントフラグメントオブジェクトに追加します。
 f.append(firstChild);
 }
 // ドキュメントフラグメントオブジェクトを返します
 return f;
 }

コンパイルテンプレート

フラグメントを渡した後、フラグメント内のノードをトラバースして、それが要素ノードなのかテキストノードなのかを判断する必要があります。

compile(fragment){
 	// 取得したドキュメントの断片を渡します。
 // 1.子ノードの取得
 const childNodes = fragment.childNodes;
 	// 子ノードの配列を繰り返し処理します。
 [...childNodes].forEach(child => {
 /*
 	isElementNode(el){
 return el.nodeType === 1; 
 }
 */
 if(this.isElementNode(child)){
 // 要素ノード
 // console.log('要素ノード', child);
 this.compileElement(child);
 }else{
 // 非要素ノード
 // console.log('テキストノード',child);
 this.compileText(child);
 }
 // 子ノードの下に子ノードがあるかどうかを判断し、あれば再帰します。
 if(child.childNodes && child.childNodes.length){
 this.compile(child);
 }
 })
 }

コンパイル要素ノード

compileElement(node){
 	// ノードのすべての属性を取得
 const attributes = node.attributes;
 	// プロパティの配列に対する反復処理
 [...attributes].forEach(attr => {
 // attrのname属性とvalue属性を別々に抽出するには、分解された代入を使用します。
 // ここでのnameとvalueはattrの属性に対応しており、変更することはできません。
 // name: v-text,v-model,v-bind....
 // vaule: v-text,v-model..の後に値が続きます。
 const { name, value } = attr;
 /*
 	名前が'v'で始まるかどうかの判定-'が先頭にあればコンパイルされ、なければ処理されません。
 isDirective{
 return attrName.startsWith('v-');
 }
 */
 if(this.isDirective(name)){
 //  -'スプリット名、spliteメソッドは配列を返し、最初のパラメータは使用されず、2番目のパラメータはディレクティブに割り当てられます。
 // ここでのディレクティブは上のものとは違うので、自分で名前をつけてください。
 const [,directive] = name.split('-');
 // なぜなら、指導は-textこれは、おそらく-on:clickこの種類は、さらに分割する必要があります。
 const [dirName,eventName] = directive.split(':');
 // データの更新 データ駆動型ビュー
 // dirNameに応じて、compileUtilの異なるメソッドを呼び出します。
 // dirName本文を参照,html,bind,on...
 complileUtil[dirName](node, value, this.vm, eventName)

 // 指示ラベルの属性削除
 node.removeAttribute(name);
 }
 })
 }

テキストノードのコンパイル

compileText(node){
 // テキストコンテンツの取得
 const content = node.textContent;
 if(/\{\{(.+?)\}\}/.test(content)){
 complileUtil['text'](node,content,this.vm)
 }
}

CompileUtil オブジェクト

異なる命令を処理するための内部実装メソッド

const complileUtil = {
 getVal(expr, vm){
 /*
 	exprメッセージかもしれないし、人かもしれません。.nameですから、もう一度exprを分割する必要があります。
 
 分割後は次のような配列になります。['person', 'name']reduceメソッド使用後
 最初にvmを返したとき.$data['person']
 	2度目のVM復帰.$data['person']['name']
 	
 	この場合も、msg なら配列['msg']つまり、そのままvmに戻るだけです.$data['msg'] 
 */
 return expr.split('.').reduce((data, currentVal) => {
 // console.log(data);
 // console.log(currentVal);
 return data[currentVal];
 }, vm.$data//初期値)
 },
 setVal(expr, vm, inputVal){
 return expr.split('.').reduce((data, currentVal) => {
 data[currentVal] = inputVal;
 }, vm.$data)
 },
 
	//  -textディレクティブとテキストノードのための mustache 構文{{msg}}
 text(node, expr, vm){
 let value;
 // で始めるかどうかを決定します。{{'を先頭に
 if(expr.indexOf("{{")!==-1){
 // 二重中括弧を正規表現でマッチさせ、中括弧内の文字列を抽出します。
 value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
 // vmのgetValメソッドを渡します。.$dataデータ検索
 return this.getVal(args[1], vm);
 })
 }else{
 value = this.getVal(expr, vm);
 }
 // ビューの更新
 this.updater.textUpdater(node, value);
 },
 //  -htmlディレクティブ
 html{
 const value = this.getVal(expr, vm);
 this.updater.htmlUpdater(node, value);
 },
 //  -modelインストラクション
 モデル{
 const value = this.getVal(expr, vm);
 //  => => 
 // 入力イベントのリスニング
 node.addEventListener('input', (e)=>{
 this.setVal(expr, vm, e.target.value);
 })
 this.updater.modelUpdater(node, value);
 },
 //  -on命令
 オン{
 let fn = vm.$options.methods && vm.$options.methods[expr];
 node.addEventListener(eventName, fn.bind(vm), false)
 },
 //  -modelコマンド
 バインド{
 ...//自分でできる実装方法は以下の通りです。
 },
 
 //機能更新
 updater: {
 textUpdater(node, value){
 node.textContent = value;
 },
 htmlUpdater(node, value){
 node.innerHTML = value;
 },
 modelUpdater(node, value){
 node.value = value;
 },
 bindUpdater(node, attrName, value){
 node[attrName] = value
 }
 }
}

データリスナーの実装 Observer

オブザーバークラスの作成

は英語の -ity、-ism、-ization に対応します。

Object.defineProperty を使用して、データ内のすべてのプロパティのゲッターとセッターを設定します。

class Observer{
 constructor(data){
 this.observer(data);
 }
 observer(data){
 if(data && typeof data === 'object'){
 // データオブジェクトの反復処理
 Object.keys(data).forEach(key => {
 this.defineReactive(data, key, data[key]);
 })
 }
 }
 defineReactive(data, key, value){
 // 再帰的走査
 this.observer(value);
 const dep = new Dep();
 // すべてのプロパティをリッスン、ハイジャック 
 // definePropertyデータを渡すと、キーがあります。[key]対応するゲッターとセッターを追加します。
 Object.defineProperty(data, key, {
 enumerable: true,
 configurable: false, //プロパティが設定されていて、削除可能かどうかを記述します。
 get(){
 // この関数は、プロパティがアクセスされたときに呼び出されます。
 //  
 // サブスクリプションデータの変更時にDepするオブザーバの追加
 Dep.target && dep.addSub(Dep.target);
 return value;
 },
 // この関数は、属性の値が変更されたときに呼び出されます。
 // ここでは矢印関数を使用し、Objectではなく上位を指すようにしています。
 set: (newVal) => {
 this.observer(newVal)
 if(newVal !== value){
 value = newVal;
 }
 //デップに変更を通知
 dep.notify();
 }
 });
 }
}

MVueクラスに実装

class MVue{
 constructor(options){
 this.$options = options;
 this.$el = options.el;
 this.$data = options.data;

 if(this.$el){
 // データオブザーバーの実装
 new Observer(this.$data);
 // 命令パーサの実装
 new Compile(this.$el, this);
 }
 }
}

依存関係コレクターの実装

Depクラスの作成

は英語の -ity、-ism、-ization に対応します。

  • オブザーバーの収集
  • Observerのハイジャックされたデータが変更された場合、Depは対応するObserverに通知するように通知されます。
class Dep{
 constructor() {
 this.subs = [];
 }
 // オブザーバの収集
 addSub{
 this.subs.push(watcher);
 }
 // オブザーバーに更新を通知
 notify(){
 this.subs.forEach(w => w.update())
 }
}

ビューを更新するWatcherの実装

Watcherクラスの作成

class Watcher{
 constructor(vm, expr, callback){
 “this”.vm = vm;
 “this”.expr = expr;
 “this”.callback = callback;
 “this”.oldVal = “this”.getOldVal()
 }
 getOldVal(){
 Dep.target = “this”;
 const oldVal = complileUtil.getVal(“this”.expr, “this”.vm)
 Dep.target = null;
 return oldVal;
 }
 update(){
 const newVal = complileUtil.getVal(“this”.expr, “this”.vm);
 if(newVal !== “this”.oldVal){
 “this”.callback(newVal);
 }
 }
}

命令を処理する compileUtil のすべてのメソッドで Watcher を実装します。

new Watcher(vm, expr, callback);

const complileUtil = {
 getContentVal(expr, vm){
 return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
 return this.getVal(args[1], vm)
 })
 },
 text(node, expr, vm){
 let value;
 if(expr.indexOf("{{")!==-1){
 value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
 new Watcher(vm, args[1], ()=>{
 this.updater.textUpdater(node, this.getContentVal(expr,vm));
 })
 return this.getVal(args[1], vm);
 })
 }else{
 value = this.getVal(expr, vm);
 }
 this.updater.textUpdater(node, value);
 },
 html(node, expr, vm){
 const value = this.getVal(expr, vm);
 // 将来データを更新するコールバックをトリガーするウォッチャーにバインドします。
 新しいウォッチャー=>{
 this.updater.htmlUpdater(node, newVal);
 })
 this.updater.htmlUpdater(node, value);
 },
 ...
}

ショーケース

この時点で、VueのMVVM応答性全体が実装され、データの双方向バインディングも実装されています。

概要

vueは、パブリッシャーサブスクライバーパターンでデータハイジャックを使用することです。Object.defineProperty();を通じて、各プロパティのセッターとゲッターをハイジャック、データが変更されると、依存関係コレクターDepにメッセージをリリースし、オブザーバに通知し、対応するコールバック関数がビューを更新するようにします。

Read next

PerfDog WEB ユーザーマニュアル

1.公式サイトホームページ l アカウント登録 まず、公式サイトにログインし、登録ボタンをクリックします:登録ボックスで、アカウント情報を入力します:ログインする前に、メールボックスで確認する必要があります:もし、メールが

Oct 30, 2020 · 6 min read