フロントエンドMVVMの原理

6154 ワード

author:    
email: [email protected]
date: 2018/3/1

MVVM背景
怠惰は人を進歩させると言われていますが、MVVMの進化史は、開発者を怠惰にするより簡単な歴史であることを証明しています.
ダイレクトDOMアクション->MVC->MVP->MVVM
最初のフロントエンドインタラクションは、直接的なDOM操作であり、最も有名なこれらのライブラリはjQueryであり、DOM APIをカプセル化し、すべてのDOM操作を簡単にする.
しかし、ページデータとインタラクションが多いと、散乱したコードがプロジェクトを維持しにくくなり、狂ってしまいます.だからMV*モードの発展がありました.
MV*モード
MVC&MVP&MVVMの3つの比較擬似コード:点我
MVC
  • Model:データモデル&手動レンダリングテンプレート
  • View:テンプレート
  • Controller:修正データ
  • MVP
  • Model:データモデル
  • View:テンプレート
  • Presenter:データの修正&手動レンダリングテンプレート
  • MVPはMVCモードの改造(ここでは改良とは言わないが、両者は実際に似ているため本質的な変化はない)であり、手動レンダリングステップ**をModelからPresenterに移行し、ViewとModelをより独立させ、より粋にしたが、別の角度から言えばPresenterの仕事も大きくなった.
    MVVM
  • Model:データモデル
  • View:特殊属性付きhtmlテンプレート
  • ViewModel:Directiveによるデータ修正&自動レンダリング
  • MVVMモードはDirectiveに依存し、テンプレートの自動レンダリングを実現し、開発者の両手を大きく解放した.この時、開発者はViewとModelに注目するだけで、効率とメンテナンス性の面で飛躍的な進歩を遂げた.
    次に、不思議なDirectiveを重点的に紹介します.
    データ変更検出方式(Directive)
    (一)手動でバインドをトリガする
    ページを変更する必要がある場合は、検出を手動でトリガーし、modelデータを変更し、要素をスキャンし、特殊なタグのある要素を変更します.
    let data = {
        value: 'hello'
    };
    
    let directive = {
        html: function (html) {
            this.innerHTML = html;
        },
        value: function (html) {
            this.setAttribute('value', value);
        }
    };
    
    ViewModelSet('value', 'hello world');
    
    function ViewModelSet(key, value) {
        data[key] = value;
        scan(); 
    }
    
    function scan() {
        for (let elem of elems) {
            elem.directive = [];
            for (let attr of elem.attributes) {
                if (attr.nodeName.indexOf('v-') >= 0) {
                    directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
                }
            }
        }
    }

    (二)汚れ検出メカニズム
    手動バインドの最適化では、変更されたデータのみが要素を更新します.
    function scan(elems, val) {
        let list = document.querySelectorAll(`[v-bind=${val}]`); //               
        for (let elem of elems) {
            for (let attr of elem.attributes) {
                let dataKey = elem.getAttribute('v-bind');
                if (elem.directive[attr.nodeValue] !== data[dataKey]) { //        ,    
                    directive[attr.nodeValue].call(elem, data[dataKey]); 
                    elem.directive[attr.nodeValue] = data[dataKey]; //        
                }
            }
        }
    }

    (三)フロントエンドデータオブジェクトハイジャック(Hijacking)
    以上の基礎をさらに進めるにはObjectを用いる.definePropertyはデータをget&setリスニングし、データが変化した場合、scanスキャンを自動的に実行し、要素を更新します.
    データを変更するときに手動scanが必要だったのか.データを直接変更するだけで、自動的にscanし、要素を更新します.
    defineGetAndSet(data, 'value');
    
    data.value = 'hello world';
    
    function defineGetAndSet(obj, propName) {
        Object.efineProperty(obj, propName, {
            get: function () {
                return this.bVal;
            },
            set: function (newVal) {
                this.bVal = newVal;
                scan();
            },
            enumerable: true,
            configurable: true
        });
    }

    (四)ECMAScript 6 Proxy
    方法3と同様に書き方を変え,ここではES 6のProxyを適用した.
    let data = new Proxy({
        get: function (obj, key) {
            return obj[key];
        },
        set: function (obj, key, val) {
            obj[key] = val;
            scan();
            return obj[key];
        }
    });

    以上です.
    参考資料
  • 『現代先端技術解析』、張成文、2017年4月第1版d
  • 『MVC、MVPとMVVMの図示』、チェン一峰、2015年2月1日、http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html