vueを真似て自分で応答式フレームワークを書く(五)終章-v-for命令実現


概要
下一篇では、vueオブジェクトの構築を実現し、変数のバインドとイベントバインドを初歩的に実現しました.今、v-for命令の実装という問題が残っています.これも本シリーズで最も難しい部分です.
難点
v-forを実現するには以下のいくつかの難点がある.
  • 式の解析、v-forには2つの構文item in items(item,index) in itemsがあり、2つ目はシーケンス番号を取得することができ、プログラムはこの2つの構文
  • を解析する必要がある.
  • v-for内の要素をコンパイルして、すでにcompile関数がありますが、v-forループ内のコンテキストとvueは一致していません.どういう意味ですか.compile内のバインド値と変数はvueで、vueはグローバルですが、v-for内のバインドされた変数はループ内で、毎回異なります.
    コンパイル
    compileでは、v-forに遭遇した場合、v-for内のノードをすべて生成してから、サブノードappendとして親ノードに渡すので、最初のステップはv-forコマンドが含まれているかどうかを判断することです.
    function isLoop(element) {
            return element.attributes && element.attributes['v-for'];
        }
    compile関数再帰コンパイルサブノード
    for (let i = 0; i < node.childNodes.length; ++i) {
                element.appendChild(compile(node.childNodes[i]));
            }
    次のように変更
    for (let i = 0; i < node.childNodes.length; ++i) {
                let child = node.childNodes[i];
                if (isLoop(child)) {
                    let ns = compileLoop(child, element);
                    for (let j = 0; j < ns.length; ++j) {
                        element.appendChild(ns[j]);
                    }
                } else {
                    element.appendChild(compile(child));
                }
            }
    compileLoopはv-forノードをコンパイルし、ノード配列を返し、親ノードは戻ったノードをappendする.
    解析
    コンパイルの第一歩は解析であり,3つの部分の内容を解析する必要がある.
  • サイクルの配列変数
  • サイクル中の変数名
  • サイクル中の要素の下付き
  • let vfor = element.attributes['v-for'].value;
    let itemName;
    let indexName;
    let varName;
    let loopExp1 = /\(([^,]+),([^\)]+)\)\s+in\s+(.*)/g;
    let loopExp2 = /(\w+)\s+in\s+(.*)/g;
    let m;
    if (m = loopExp1.exec(vfor)) {
        itemName = m[1];
        indexName = m[2]
        varName = m[3];
    } else if (m = loopExp2.exec(vfor)) {
        itemName = m[1];
        varName = m[2];
    }
    直接正則で解析します.loopExp 1とloopExp 2はそれぞれ2つの構文に対応します.varName:配列名、itemName:ループ変数名、indexName:ループ下付き
    要素の生成
    解析が完了すると要素の生成を開始できます
    var directive = {
        origin: element.cloneNode(true),
        attr: 'v-for',
        exp: {
            varName: varName,
            indexName: indexName,
            itemName: itemName
        }
    }
    element.attributes.removeNamedItem('v-for');
    let arrays = vue[varName];
    let elements = [];
    for (let i = 0; i < arrays.length; ++i) {
        vue[itemName] = arrays[i];
        vue[indexName] = i;
        elements.push(compile(element.cloneNode(true), false));
    }
    if (!loopElement[varName]) {
        let loop = {};
        loop.elements = elements;
        loop.parent = parent;
        loopElement[varName] = loop;
    }
  • は変数directiveを定義し、v-forのいくつかの文法も保存し、次回は直接使用することができ、
  • を再解析する必要はありません.
  • cloneで生成されるため、v-forラベルを削除する必要があります.そうしないと、デッドサイクル
  • に入ります.
  • 再帰呼び出しcompileは新しい要素を生成し、ループごとに現在の変数と下付きラベルをvueに配置し、コンパイル時にプログラムが変数
  • を見つけることを保証する.
    for (let i = 0; i < arrays.length; ++i) {
        vue[itemName] = arrays[i];
        vue[indexName] = i;
        elements.push(compile(element.cloneNode(true), false));
    }
  • 結果をloopElementに保存する目的は、バインドされた配列が変化すると、現在の関連ノードを削除して新しいノード
  • を再生成する必要があることである.
    インストラクション
    directive.change = function (name, value) {
        let ele = loopElement[name];
        for (let i = 0; i < ele.elements.length; ++i) {
            ele.elements[i].remove();
        }
        let newEles = [];
        let arrays = vue[this.exp.varName];
        for (let i = 0; i < arrays.length; ++i) {
            vue[this.exp.itemName] = arrays[i];
            vue[this.exp.indexName] = i;
            let node = compile(this.origin.cloneNode(true));
            newEles.push(node);
        }
        loopElement[name].elements = newEles;
        for (let j = 0; j < newEles.length; ++j) {
            ele.parent.appendChild(newEles[j]);
        }
    }
    addSubscriber(varName, directive);
  • 現在の要素を除去する
  • .
  • 上の論理と同様に、新しい要素
  • を生成する
  • 以前に保存したparentによりappend
  • を行う.
  • addSubscriberサブスクライバを作成サブスクライバにコマンドを登録する
  • 完全なcompileLoopコードは以下の通りです.
    function compileLoop(element, parent) {
        let vfor = element.attributes['v-for'].value;
        let itemName;
        let indexName;
        let varName;
        let loopExp1 = /\(([^,]+),([^\)]+)\)\s+in\s+(.*)/g;
        let loopExp2 = /(\w+)\s+in\s+(.*)/g;
        let m;
        if (m = loopExp1.exec(vfor)) {
            itemName = m[1];
            indexName = m[2]
            varName = m[3];
        } else if (m = loopExp2.exec(vfor)) {
            itemName = m[1];
            varName = m[2];
        }
        var directive = {
            origin: element.cloneNode(true),
            attr: 'v-for',
            exp: {
                varName: varName,
                indexName: indexName,
                itemName: itemName
            }
        }
        element.attributes.removeNamedItem('v-for');
        let arrays = vue[varName];
        let elements = [];
        for (let i = 0; i < arrays.length; ++i) {
            vue[itemName] = arrays[i];
            vue[indexName] = i;
            elements.push(compile(element.cloneNode(true), false));
        }
        if (!loopElement[varName]) {
            let loop = {};
            loop.elements = elements;
            loop.parent = parent;
            loopElement[varName] = loop;
        }
        directive.change = function (name, value) {
            let ele = loopElement[name];
            for (let i = 0; i < ele.elements.length; ++i) {
                ele.elements[i].remove();
            }
            let newEles = [];
            let arrays = vue[this.exp.varName];
            for (let i = 0; i < arrays.length; ++i) {
                vue[this.exp.itemName] = arrays[i];
                vue[this.exp.indexName] = i;
                let node = compile(this.origin.cloneNode(true));
                newEles.push(node);
            }
            loopElement[name].elements = newEles;
            for (let j = 0; j < newEles.length; ++j) {
                ele.parent.appendChild(newEles[j]);
            }
        }
        addSubscriber(varName, directive);
        return elements;
    }
    イベントレスポンス
    下一篇では、私たちのイベント応答はこのように書かれています.
    function addEvent(element, event, method) {
        element.addEventListener(event, function(e) {
            let params = [];
            let paramNames = method.params;
            if (paramNames) {
                for (let i = 0; i < paramNames.length; ++i) {
                    params.push(vue[paramNames[i]]);
                }
            }
            vue[method.name].apply(vue, params);
        })
    }
    このように書くと、ループのたびに下付きおよびループ変数がリセットされるため、下付きおよびループ変数はvueオブジェクトに保存されるため、イベントがトリガーされるとparams.push(vue[paramNames[i]]);行のコードはコンテキストが変化したため、値を取得できません.この問題を解決する方法は,パケットを閉じて当時の環境情報を保存することで,実行時に失われることなく,取得データを外に移動するだけでよい.
    function addEvent(element, event, method) {
        let params = [];
        let paramNames = method.params;
        if (paramNames) {
            for (let i = 0; i < paramNames.length; ++i) {
                params.push(vue[paramNames[i]]);
            }
        }
        element.addEventListener(event, function (e) {
            vue[method.name].apply(vue, params);
        })
    }
    ここでv-for命令を実装することができますが、以前のいくつかの残留物はまだ修復されていません.dom解析では、テキストのノード値が変化したのは、以下のように簡単なテキスト置換にすぎません.
    if (node.nodeType == 3) {
        directive.change = function(name, value) {
            this.node.textContent = this.origin.replace("\{\{" + name + "\}\}", value);
        }
    } 
    複数の変数やtodo.textのようなマルチレベル変数の結果がエラーになる場合は、表現を解析するための関数を書きます.
    if (node.nodeType == 3) {
        directive.change = function (name, value) {
            this.node.textContent = evaluteExpression(this.origin);
        }
    } 
  • evaluteExpression
  • function evaluteExpression(text) {
        let vars = parseVariable(text);
        for (let i = 0; i < vars.length; ++i) {
            let value = getVariableValue(vars[i]);
            text = text.replace("\{\{" + vars[i] + "\}\}", value);
        }
        return text;
    }
  • まず変数を解析する
  • ループ変数値を取得し、getVariableValue
  • を呼び出す.
  • サイクル置換
  • getVariableValue
  • function getVariableValue(name) {
        let value;
        if (name.indexOf(".")) {
            let ss = name.split(".");
            value = vue[ss[0]];
            if (value) {
                for (let i = 1; i < ss.length; ++i) {
                    value = value[ss[i]];
                    if (value == undefined) {
                        break;
                    }
                }
            }
        } else {
            value = vue[name];
        }
        if (value == undefined || value == null) {
            value = "";
        }
        return value;
    }
  • は、item.textのような多段変数を循環取得する
  • である.
  • 未定義が空白文字列に設定されている場合
  • 効果
    以下は実現した効果図で、ここをクリックして表示することもできます
    完全なjsコードはここをクリックして見ます
    リファレンス
    次のリンクをクリックして、このシリーズの他の記事を表示します.
  • vueを真似て自分で応答式フレームワークを書く(一)-Vueはtodo応用を実現する
  • vueを真似て応答フレームワークを自分で書きます(二)-Vueオブジェクト作成用
  • vueを真似て自分で応答式フレームワークを書く(三)-dom解析
  • vueを真似て応答フレームワークを自分で書きます(4)-Vueオブジェクト構築
  • vueを真似て自分で応答式フレームワークを書く(五)終章–v-for命令実現