アプリケーションアーキテクチャの紹介

Ext JS は MVC と MVVM の両方のアプリケーションアーキテクチャをサポートしています。これらのアーキテクチャのアプローチはいずれも特定の概念を共有し、ロジック行に従ってアプリケーションコードを分割します。アプリケーションを分割する方法によって各アプローチに長所があります。

本ガイドの目的は、これらのアーキテクチャを構成するコンポーネントに関する基本的な知識を提供することです。

MVC とは

MVC アーキテクチャでは、ほとんどのクラスは Model、View、Controller のいずれかです。ユーザーは Model に保持されているデータを表示する View を操作します。これらの操作は Controller によって監視されます。これは、View と Model を必要に応じて更新することで操作に対応します。

更新の指示を出すのはControllerのみのため、View と Model は通常は相互に認識しません。一般的に、Controller は MVC アプリケーション内のアプリケーションロジックのほとんどを含みます。View は(ある場合に)ビジネスロジックをできるだけ少なくするのが理想です。Model は主にデータへのインターフェイスでデータへの変更を管理するビジネスロジックを含みます。

MVC の目的はアプリケーション内の各クラスに対する責任を明確に定義することです。クラスはすべて明確に定義された責任があるため、暗示的により大きな環境から分離されます。これはアプリのテストとメンテナンスを容易にし、コードを再利用しやすくします。

MVVM とは

MVC と MVVM の主な違いは MVVM が ViewModel と呼ばれる View の抽象化を搭載している点です。ViewModel は Model のデータと View のそのデータのプレゼンテーションの変更を「データバインディング」と呼ばれる手法で調整します。

その結果、Model とフレームワークはできるだけ作業を行い、View を直接操作するアプリケーションのロジックを最小化または排除します。

以前からのユーザー

Ext JS 5 では MVVM アーキテクチャのサポートと MVC での (C) の改良が行われています。これらの機能強化を調べて活用するようにお勧めしますが、既存の Ext JS 4 MVC アプリケーションが引き続き変更なしで機能するように最大限の努力を払っています。

MVC と MVVM

これらがどのようにアプリケーションに適合するか理解するために、それぞれの略語の意味を最初に定義します。

  • (M) Model - これはアプリケーション用のデータです。クラスのセット(「Model」という)はデータのフィールドを定義します(ユーザー名とパスワードフィールドのある User Model など)。Model はデータパッケージを通じて存続方法を理解し、アソシエーションを通じて他の Model とリンクできます。

    Modelは通常グリッドや他のコンポーネント用のデータを提供するためにストアと共に 使われます。 Modelはまた、必要な、バリデーションや変換など、どのようなデータロジックにおいても 理想的なロケーションです。

  • (V) View - View は表示されるすべてのタイプのコンポーネントです。例えば、グリッド、ツリー、パネルはすべて View と考えられます。

  • (C) Controller - Controller はアプリを機能させる View のロジックを維持する場所として使用されます。これには、ビューの描画、Model のインスタンス化、その他のアプリロジックが必要になります。

  • (VM) ViewModel - ViewModel は View に特定のデータを管理するクラスです。これによって、対象コンポーネントをバインドさせ、このデータが変更されるたびに更新できます。

これらのアプリケーションアーキテクチャはフレームワークコードに対して構造と整合性を提供します。推奨表記規則に従うことで多くの重要な利点がもたらされます。

  • どのアプリケーションも同じように動作するので、一度学習すれば十分です

  • アプリケーション間のコード共有は簡単です。

  • Sencha Cmd を使用してアプリケーションの最適化された本番用のバージョンを作成できます。

サンプルアプリのビルド

個別に見ていく前に、Sencha Cmd でサンプルアプリをビルドしてみましょう。コマンドラインから以下のコマンドを発行するだけです。

sencha generate app -ext MyApp ./app
cd app
sencha app watch

注意:上記の内容に精通していない場合、はじめにを参照してください。

アプリケーションの概要

MVC、MVVM、MVC+VM パターンの各要素を見ていく前に、Cmd が生成したアプリケーションの構造を見てみましょう。

ファイル構造

Ext JS アプリケーションは、すべてのアプリで同じ統一されたディレクトリ構造に従います。推奨レイアウトでは、すべてのクラスが app フォルダに配置されます。このフォルダには ModelsStores、View 要素の名前空間を格納するサブフォルダが含まれます。View、ViewControllerViewModels などの View 要素は、整理上のベストプラクティスとしてグループ化することが推奨されます(以下の “main” ビューフォルダ参照)。

名前空間

各クラスの最初の行はソートのアドレスです。この「アドレス」は名前空間と呼ばれます。名前空間の式は次のようになります。

<AppName>.<foldername>.<ClassAndFileName>

サンプルアプリでは、「MyApp」が AppName、「view」がフォルダ名、「main」がサブフォルダ名、「Main」が Class と Filename です。この情報に従って、フレームワークは以下の場所の Main.js と呼ばれるファイルを探します。

app/view/main/Main.js

ファイルが見つからない場合、その状況に対処するまで Ext JS はエラーをスローします。

アプリケーション

index.html を見てアプリケーションの評価を開始しましょう。

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">

    <title>MyApp</title>

    <!-- The line below must be kept intact for Sencha Cmd to build your application -->
    <script id="microloader" type="text/javascript" src="bootstrap.js"></script>

</head>
<body></body>
</html>

Ext JS はマイクロローダーを使用して app.json ファイルに記述されているアプリケーションリソースをロードします。これは index.html にリソースを追加する代わりとなります。app.json では、すべてのアプリケーションのメタデータが1ヶ所に存在します。従って、Sencha Cmd はシンプルで効率的な方法でアプリケーションをコンパイルできます。

app.json にはコメントが多く、それ自体が受け入れる情報を収集するための優れたリソースを提供します。

app.js

アプリケーションを事前に生成した際には、クラスを(Application.js)で作成し、そのインスタンスを(app.js)で起動しました。app.js のコンテンツを以下のように参照できます。

/*
 * このファイルは Sencha Cmd によって生成され更新されます。このファイルは
 * アプリケーションの必要に応じて編集できますが、これらの編集は
 * アップグレード時に Sencha Cmd によってマージする必要があります。
 */
Ext.application({
    name: 'MyApp',

    extend: 'MyApp.Application',

    autoCreateViewport: 'MyApp.view.main.Main'

    //-------------------------------------------------------------------------
    // カスタマイズのほとんどは MyApp.Application に行う必要があります。
    //このファイルをカスタマイズする必要がある場合、このセクションの下のように行うと、
    // Sencha Cmdの新しいバージョンへのアップグレード時にマージ競合の可能性が減少します。
    //-------------------------------------------------------------------------
});

autoCreateViewport は Ext JS 5 の新しい機能です。autoCreateViewport に Container クラスを設計することによって、Viewport としてクラスを使用できます。上記の例では、MyApp.view.main.MainContainer クラス)を Viewport と判断しました。

autoCreateViewport コンフィグはアプリケーションに設計されたビューを作成し、Viewport Plugin を付加するように指示します。これによってビューがドキュメント本体に接続されます。

Application.js

すべての Ext JS アプリケーションは Application クラスのインスタンスで始まります。このクラスは app.js が起動し、テスト用にインスタンス化できるように設計されています。

以下の Application.js コンテンツは Sencha Cmd でアプリケーションを生成すると自動的に作成されます。

Ext.define('MyApp.Application', {
    extend: 'Ext.app.Application',

    name: 'MyApp',

    stores: [
        // TODO: add global/shared stores here
    ],

    launch: function () {
        // TODO - Launch the application
    }
});

Application クラスはアプリの名前空間、共有ストアなど、アプリケーションのグローバル設定を含みます。

Views(ビュー)

ビューは、Ext.Component のサブクラスで、単なるコンポーネントに過ぎません。ビューはアプリケーションのすべての視覚要素を含みます。

スターターアプリの Main.js を「main」フォルダの下から開くと、以下のコードが表示されます。

Ext.define('MyApp.view.main.Main', {
    extend: 'Ext.container.Container',

    xtype: 'app-main',

    controller: 'main',
    viewModel: {
        type: 'main'
    },

    layout: {
        type: 'border'
    },

    items: [{
        xtype: 'panel',
        bind: {
            title: '{name}'
        },
        region: 'west',
        html: '<ul>...</ul>',
        width: 250,
        split: true,
        tbar: [{
            text: 'Button',
            handler: 'onClickButton'
        }]
    },{
        region: 'center',
        xtype: 'tabpanel',
        items:[{
            title: 'Tab 1',
            html: '<h2>Content ...</h2>'
        }]
    }]
});

ビューにはアプリケーションロジックは含まれない点に注意してください。ビューの論理ビットはすべて ViewController に含める必要があります。これについては次のセクションで説明します。

このビューは West、Center リージョンに Border レイアウトのあるコンテナを定義します。これらのリージョンには ボタンタブパネルを単一のタブに含むツールバーのあるパネルが含まれます。これらの概念に精通していない場合、はじめにを参照してください。

Controller と viewModel コンフィグはこのビューの2つの要素です。

Controller コンフィグ

Controller コンフィグを使用することによって、ビューに ViewController を指定できます。このように ViewController がビューに対して指定されている場合、イベントハンドラとリファレンスのコンテナになります。これによって、ViewController にビューから発火されたイベントとコンポーネントと1対1の関係を与えます。次のセクションでコントローラに関して説明します。

ViewModel コンフィグ

viewModel コンフィグを使用するとビューに対して ViewModel を指定できます。ViewModel はこのコンポーネントとその子ビューに対してデータを提供します。ViewModel に含まれるデータは、このデータを表示または編集させたいコンポーネントに bind コンフィグを追加することによって通常使用されます。

上記のビューでは、West リージョンのパネルのタイトルが ViewModel にバインドされていることが分かります。これは、ViewModel で管理されるデータの “name” 値によってタイトルが読込まれることを意味します。ViewModel のデータが変更されると、タイトルの値が自動的に更新されます。ViewModel についてはこのドキュメントで後述します。

Controller(コントローラ)

次に、コントローラについて見てみましょう。ViewController MainController.js が生成するスターターアプリは次のようになります。

Ext.define('MyApp.view.main.MainController', {
    extend: 'Ext.app.ViewController',

    requires: [
        'Ext.MessageBox'
    ],

    alias: 'controller.main',

    onClickButton: function () {
        Ext.Msg.confirm('Confirm', 'Are you sure?', 'onConfirm', this);
    },

    onConfirm: function (choice) {
        if (choice === 'yes') {
            //
        }
    }
});

ビュー Main.js を再度見てみると、tbar ボタンのハンドラに関数の指定があることが分かります。そのハンドラにはこのコントローラの onClickButton と呼ばれる関数がマップされています。ご覧の通り、このコントローラは特殊設定のないイベントに対処できます。

これによって、アプリケーションにロジックを非常に簡単に追加できます。コントローラにビューと1対1の関係があるので、onClickButton 関数を定義するだけです。

ビューのボタンをクリックすると、メッセージボックスが作成されます。メッセージボックスには、この同じコントローラ専用の onConfirm 関数呼び出しが含まれます。

ViewController は次の目的で設計されています。

  • “listeners” と “reference” コンフィグを使用してビューへの接続を明らかにします。

  • ビューのライフサイクルを活用してその関連 ViewController を自動的に管理します。インスタンス化から破棄に至るまで、Ext.app.ViewController は参照されたコンポーネントに結び付けられています。同じ view クラスの第2のインスタンスは専用の ViewController インスタンスを取得します。これらの view が破棄されると、関連した ViewController インスタンスも破棄されます。

  • ビューのネストを分かりやすくするカプセル化を提供します。

ViewModels

次に、ViewModel を見てみましょう。MainModel.js ファイルを開くと、以下のコードがあります。

Ext.define('MyApp.view.main.MainModel', {
    extend: 'Ext.app.ViewModel',

    alias: 'viewmodel.main',

    data: {
        name: 'MyApp'
    }

    //TODO - add data, formulas and/or methods to support your view
});

ViewModel はデータオブジェクトを管理するクラスです。このクラスを使用すると、このデータの対象ビューをバインドさせ、変更を通知できます。ViewController などの ViewModel はそれを参照するビューが所有します。ViewModel はビューに関連付けされているため、コンポーネント階層の親コンポーネントが所有する親 ViewModel にもリンクできます。これによって、子ビューは親 ViewModel のデータを単に「継承」することができます。

ビューから ViewModel へのリンクを Main.js の ViewModel で作成しました。このリンクによって、セッターが自動的に viewModel からのデータを宣言によってビューに設定できるようにコンフィグをバインドできます。このデータは MainModel.js の例でインラインにより示されています。ここで、データはどこから取得しても、何でも構いません。プロキシ(AJAX、REST など)からの提供データでも構いません。

Model と Store

ModeStore はアプリケーションの情報ゲートウェイとなります。データのほとんどはこれらの2つのクラスによって送信、取得、整理、「モデル化」されます。

Model

Ext.data.Model はアプリケーション内の永続タイプのデータを示します。各 model にはフィールドと関数があり、これによってアプリケーションはデータを「モデル化」できます。通常、Model は Store と一緒に使用されます。また、Store はグリッド、ツリー、チャートなどデータにバインドされたコンポーネントで使用できます。

サンプルアプリケーションは現在 Model を含まないので、以下のコードを追加してみましょう。

Ext.define('MyApp.model.User', {
    extend: 'Ext.data.Model',
    fields: [
        {name: 'name',  type: 'string'},
        {name: 'age',   type: 'int'}
    ]
});

上記の名前空間で説明したように、app/model の下でも残るように User.js を作成します。

Fields

Ext.data.Model は値または “fields” と呼ばれるプロパティを含むレコードを記述します。Model クラスは “fields” コンフィグを使用してこれらのフィールドを宣言できます。このクラスでは、“name” が string と宣言され、年齢は integer です。API ドキュメントで利用できる field タイプは他にもあります。

フィールドとそのタイプを宣言するにはそれなりの理由がありますが、必ずしも宣言する必要はありません。fields コンフィグを含めないと、データが自動的に読み込まれ、データオブジェクトに挿入されます。データに対して以下が必要な場合に、フィールドを定義します。

  • バリデーション

  • デフォルト値

  • Convert 関数

ストアを設定して、これらの2つが機能するか見てみましょう。

ストア

ストアはクライアントサイドのレコードのキャッシュです(Model クラスのインスタンス)。ストアでは、その中に含まれるモデルのレコードの並べ替えやフィルタリング、クエリを行う関数を提供します。

このサンプルアプリケーションにストアは含まれませんが、心配いりません。ストアを定義して、Model を割り当てるだけです。

Ext.define('MyApp.store.User', {
    extend: 'Ext.data.Store',
    model: 'MyApp.model.User',
    data : [
     {firstName: 'Seth',    age: '34'},
     {firstName: 'Scott', age: '72'},
     {firstName: 'Gary', age: '19'},
     {firstName: 'Capybara', age: '208'}
    ]
});

上記のコンテンツに User.js を追加します。これは app/store に配置されます。

ストアのグローバルインスタンスが必要な場合、ストアを Application.js の Store コンフィグに追加することができます。Application.js の Store コンフィグは次のようになります。

stores: [
    'User'
],

この例では、ストアは直接データを含みます。現実的にはほとんどの場合、プロキシをモデルまたはストアで使用してデータを収集する必要があります。プロキシを使用することによって、データプロバイダとアプリケーション間でデータを転送できます。

モデル、ストア、データプロバイダに関してはデータガイドで詳細に読むことができます。

次のステップ

Ticket App と呼ばれる堅牢で便利なアプリケーションを作成しました。このアプリケーションはログイン/ログアウトセッションを管理し、データバインディングを統合し、MVC+VM アーキテクチャを使用する際の「ベストプラクティス」を表示します。この例はコメントが多く付けられているので、すべてが可能な限り明確になっています。

Ticket App について時間をかけて調べ、理想的な MVC+VM アプリケーションアーキテクチャについて詳しく知ることをお勧めします。

Last updated