2009 年頃から、MVC はフロントエンドの分野で徐々に頭角を現し、2015 年の React Native の立ち上げでついに大爆発を迎えました: AngularJS、EmberJS、Backbone、ReactJS、RiotJS、VueJS... ..一連の名前が出現し、派手に変化してきましたが、その中には徐々に人々の目から消えていくものもあれば、現在も急速に成長しているものもあれば、すでに特定の生態環境の中で独自の役割を果たしているものもあります。しかし、何があっても、MVC はフロントエンド エンジニアの考え方や仕事のやり方に大きな影響を与えてきましたし、これからも影響し続けるでしょう。
MVC を説明する多くの例は、Backbone のコレクションや AngularJS のモデルなど、特定のフレームワークの特定の概念から始まります。これは確かに良いアプローチです。しかし、フレームワークがクラス ライブラリ (jQuery) やツール セット (アンダースコア) ではなくフレームワークである理由は、その背後に多くの優れた設計コンセプトとベスト プラクティスがあるためです。これらの設計エッセンスは相互に補完し、連動しており、が不可欠ですが、複雑なフレームワークを通して短期間で特定のデザインパターンの本質を理解するのは簡単ではありません。
これがこのエッセイの原点です。誰もが概念を理解できるように作成されたプロトタイプ コードは、できる限り単純である必要があります。誰もが概念を理解できる程度に単純である必要があります。
1. MVC の基礎はオブザーバー パターンであり、これがモデルとビュー間の同期を実現するための鍵となります
簡単にするために、各モデル インスタンスにはプリミティブ値が 1 つだけ含まれています。
function Model(value) { this._value = typeof value === 'undefined' ? '' : value; this._listeners = []; } Model.prototype.set = function (value) { var self = this; self._value = value; // model中的值改变时,应通知注册过的回调函数 // 按照Javascript事件处理的一般机制,我们异步地调用回调函数 // 如果觉得setTimeout影响性能,也可以采用requestAnimationFrame setTimeout(function () { self._listeners.forEach(function (listener) { listener.call(self, value); }); }); }; Model.prototype.watch = function (listener) { // 注册监听的回调函数 this._listeners.push(listener); };
// html代码: <div id="div1"></div> // 逻辑代码: (function () { var model = new Model(); var div1 = document.getElementById('div1'); model.watch(function (value) { div1.innerHTML = value; }); model.set('hello, this is a div'); })();
オブザーバー パターンの助けを借りて、モデルの set メソッドがその値を変更するために呼び出されるとき、テンプレートも同期的に更新されることがわかりました。しかし、この実装は、変更を手動で監視する必要があるため、非常に扱いにくいです。モデル値を (watch メソッドを介して) 取得し、コールバック関数に渡します。ビュー (1 つ以上の dom ノード) をモデルにバインドすることを簡単にする方法はありますか?
2. binding メソッドを実装し、モデルとビューをバインドします
Model.prototype.bind = function (node) { // 将watch的逻辑和通用的回调函数放到这里 this.watch(function (value) { node.innerHTML = value; }); };
// html代码: <div id="div1"></div> <div id="div2"></div> // 逻辑代码: (function () { var model = new Model(); model.bind(document.getElementById('div1')); model.bind(document.getElementById('div2')); model.set('this is a div'); })();
シンプルなカプセル化により、複数のビューをバインドする必要がある場合でも、ビューとモデル間のバインディングが簡単に実装できます。バインドは Function クラスのプロトタイプのネイティブ メソッドですが、作者はバインドという言葉を非常に気に入っているので、ここでは単にネイティブ メソッドについて説明します。 。さらに言えば、バインディングの複雑さは軽減されましたが、このステップでは依然としてバインディング ロジックをビジネス コードから完全に分離することが可能でしょうか。
3. ロジック コードからバインディングを分離するコントローラーを実装します
注意深い人は、MVC について話しているにもかかわらず、上の記事には Model クラスしか登場していないことに気づいたかもしれません。結局のところ、HTML は既成の View であることは理解できます。 、この記事でも最初から最後まで言及していますが、HTML を View として使用するだけでは、View クラスは JavaScript コードに表示されません)、では、なぜコントローラー クラスが表示されないのでしょうか。心配しないでください。実際、いわゆる「ロジック コード」は、フレームワーク ロジック (この記事のプロトタイプをフレームワークと呼ぶことにします) とビジネス ロジックの間の高度な結合を持つコード セグメントです。これを分解してみましょう。
バインディング ロジックをフレームワークに任せたい場合は、バインディングを完了する方法をフレームワークに指示する必要があります。 JS でアノテーションを完了するのは難しいため、ビューでこのマークアップ層を実行できます。HTML のタグ属性を使用するのが簡単で効果的な方法です。
function Controller(callback) { var models = {}; // 找到所有有bind属性的元素 var views = document.querySelectorAll('[bind]'); // 将views处理为普通数组 views = Array.prototype.slice.call(views, 0); views.forEach(function (view) { var modelName = view.getAttribute('bind'); // 取出或新建该元素所绑定的model models[modelName] = models[modelName] || new Model(); // 完成该元素和指定model的绑定 models[modelName].bind(view); }); // 调用controller的具体逻辑,将models传入,方便业务处理 callback.call(this, models); }
// html: <div id="div1" bind="model1"></div> <div id="div2" bind="model1"></div> // 逻辑代码: new Controller(function (models) { var model1 = models.model1; model1.set('this is a div'); });
そんなに簡単ですか?それはとても簡単です。 MVC の本質は、コントローラー内でビジネス ロジックを完成させ、同時にモデルを変更することで、これらのロジックが上記のコードに反映され、複数のビューと複数のモデルをサポートします。本番プロジェクトには十分ではありませんが、皆様の MVC 学習に少しでもお役に立てれば幸いです。
コメントが削除された整理された「フレームワーク」コード:
function Model(value) { this._value = typeof value === 'undefined' ? '' : value; this._listeners = []; } Model.prototype.set = function (value) { var self = this; self._value = value; setTimeout(function () { self._listeners.forEach(function (listener) { listener.call(self, value); }); }); }; Model.prototype.watch = function (listener) { this._listeners.push(listener); }; Model.prototype.bind = function (node) { this.watch(function (value) { node.innerHTML = value; }); }; function Controller(callback) { var models = {}; var views = Array.prototype.slice.call(document.querySelectorAll('[bind]'), 0); views.forEach(function (view) { var modelName = view.getAttribute('bind'); models[modelName] = models[modelName] || new Model(); models[modelName].bind(view); }); callback.call(this, models); }
後記:
筆者在學習flux和redux的過程中,雖然掌握了工具的使用方法,但只是知其然而不知其所以然,對ReactJS官方文件中一直強調的"Flux eschews MVC in favor of a unidirectional data flow"不甚理解,始終覺得單向資料流和MVC並不衝突,不明白為什麼在ReactJS的文檔中這二者會被對立起來,有他無我,有我無他(eschew,避開)。終於下定決心,回到MVC的定義上重新研究,雖然平日工作里大大咧咧複製粘貼,但是咱們偶爾也得任性一把,咬文嚼字一番,對吧?這樣的方式也的確幫助了我對這句話的理解,這裡可以把自己的思考分享給大家:之所以覺得MVC和flux中的單向資料流相似,可能是因為沒有區分清楚MVC和觀察者模式的關係所造成的-MVC是基於觀察者模式的,flux也是,因此這種相似性的由來是觀察者模式,而不是MVC和flux本身。這樣的理解也在四人組的設計模式原著中得到了印證:"The first and perhaps best-known example of the Observer pattern appears in Smalltalk Model/View/Controller (MVC), the user interface framework in the Smalltalk environment [KP88]. MVC's Model class plays the role of Subject, while View is the base class for observers. "。
如果讀者有興趣在這樣一個原型玩具的基礎上繼續拓展,可以參考下面的一些方向:
一個完善的框架要經過無數的提煉和修改,這裡只是最初最初的第一步,道路還很漫長,希望大家再接再厲。