1年のAngular + Typescript + Offline SPAプロジェクトの経験

画像 Angular + Typescriptに基づいたプロジェクトについて、1年間で少し前向きな経験を共有したいと思います。 これは新しいものではなく、多くの人がそれをうまく使っていると確信しています。 もちろん、多くの人が既にReactまたはAngular 2.0に関するさらなる記事を待っていますが、この経験は誰かに役立つと思われます。



コンテキスト



私は食料品会社で働いています。 主な製品は、10年以上の歴史を持つ企業アプリケーションです(Web、ASP.Net、C#MSSQL)。 名誉ある時代とコードベース1M + LoCにもかかわらず、プロジェクトは依然として関連性があり、サポートされており、ライブラリとアプローチのリファクタリングと更新が体系的に実行されています。



しかし、ここでクライアントは、メインUIをSPA(Angular)に置き換えて、デバイスで動作するようにしたいと考えました。 -新しいプロジェクトは1年前に始まりました。



それ以前は、バックエンドC#パートでアーキテクトとして働き、ビジネスルール、SQLパフォーマンス、SQLデッドロック、キューなどに取り組みました。



Javascriptを使用して、彼はペットプロジェクトにのみ積極的に取り組み、コードの量はそれほど重要ではありませんでした。 15k LoCの後に、1つのペットプロジェクトをjavascriptからjava(GWTトランスレータ)に転送する必要さえありました! プロジェクトがサポートされなくなったという事実のため。 厳しい移行が報われました(Angularが登場するまで)。



したがって、20k LoCを超えるjavascriptプロジェクトを作成し、同時にそのサポート、可読性、拡張性を節約する方法を想像するのは困難でした。



AngularのBackboneで現在行っている同様のSPAプロジェクトを分析したところ、このことを改めて確信しました-彼らは20k-35k LoCを持ち、構造は明確ではありませんでしたが、それらのリファクタリングはもはや不可能でした。



必要条件



新しいAngularプロジェクトの要件は、現在のプロジェクトの2〜4倍と推定されました。



さらに、要件にはHTML5オフラインモードのサポートも必要であり、データ編集のサポートも必要です(つまり、RESTに加えて、IndexedDBと同様の実装がまだあります)。



プロジェクトの開始は1〜2か月で計画されました。 したがって、現在のjavascriptチームの現在の経験を調査し、新しいjavascript(Angularエコシステム)を入力するのに十分なカレンダー時間がありました。



意思決定



角度



議論さえされていません。 1〜2年使用したチームがありました。 顧客はそれを主張しました。 彼はまた、私を感心させ、きれいなjQuery、GWT UI、ヘビーウェイトUIツールキットでの長年の経験の後、新鮮な空気のように見えました。

ノックアウトと古き良きバックボーンは、オブジェクト/メソッドのモデルを構築することを要求しました。 私はそのような「制限」を完全に食い尽くしました。 AngularでプレーンJavascriptオブジェクト/配列を使用する機能は、Knockout、Backboneのパフォーマンス上の利点を上回りました。



一方、Angularには独自の特性があります-独自のDIと過剰設計です。



サービス、モジュールはコード(クラスなど)を構造化するためのユニットであり、ファクトリー、コントローラーはサービスを取り巻くシュガーです。 Angular DIを使いたくなかったのですが、これもWebStorm 9を完全に混乱させました。



タイプスクリプト



私が長い間考えていた主な解決策は、試して、プロトタイプを作成し、クライアントに昇進させました。 ちなみに、クライアント(およびチーム)に、特に長年にわたって設計された戦略的なプロジェクトで流行しているJavascriptではなく、別の言語で記述する理由を説明するのはかなり困難でした。



基本的なメッセージを理解することが重要です-Typescriptは別の言語ではなく、Javascript拡張機能です(LESSがCSSプリプロセッサであるように)。

コンパイル時の型チェックが必要な理由を理解することも重要です(C#、Java、C ++との類推による)。





私の観点からすると、Typescriptキラー機能はオプションの入力です。



私がGWTで作業したとき(あなたはIDEでjavaで記述し、コンパイラはそれをjavascriptに変換します)、すべてのもの、すべての変数に型でマークする必要があることを気にしました、これは高価で不要です。 アプリケーション内の特定のものだけを型で指定したい(モジュール、データ、モデルへの変換、引数、mn関数の戻り値の間の契約)。



ちなみに、C#、Java、C ++もオプションの入力(var、auto、dynamic)に移行しています。



strict-JavascriptコードをTypescriptファイルに単純にコピーすると、問題なくコンパイルされます。 型チェックが不要な場合は、単純に特別な型「any」になり、すべてが可能です。



var a=<any>0; // 1 var a:any=0; // 2 a().some().thing()[15].else();
      
      





ジェネリック型のサポートは特に便利であり(C#やJavaと同様)、ES6 Arrow関数とともにすべての変換とPromiseの処理が機能し、モデルは非常に読みやすく安全です。



 function loadUsers():ng.IPromise<Array<User>>{ /*some*/} function loadBirthDates():ng.IPromise<Array<Date>>{ return loadUsers().then(users=>users.map(u=>u.birthDate)); }
      
      





ここで、IDEはu-Userクラスがあることを明確に認識しており、birthDateが存在しないか、Date型がない場合はエラーをスローします。



TypescriptでのES6クラスのサポート。



一見、ES6クラスは単なる砂糖です(たとえば、プロトタイプを生成するTypescriptコンパイラであるbabeljs)。 しかし、ここでは実行時の問題ではなく、IDEをサポートする問題であり、何百万人ものプログラマーが機能スタイルではなくOOPでより多くのことを考えています。



例1:



 function a(){ function b(){ function (){ function d(){} } } }
      
      





このコードはOOPスタイルで書かれているとしましょう。モジュールはどこにあり、クラスはどこにあり、クラスメソッドはどこにあるのでしょうか。 それとも、これはプライベートメソッドbのすべてのクラスで、どのwithですか? たぶんこの例は手に負えないかもしれませんが、プロトタイプを使用した場合と同じ方法でコードを作成することもあります。



例2:



 module a{ class b{ c(){ var d = ()=>{}; } } }
      
      





ここでは、OOPの用語のレベルは説明なしですでに明確になっています。 さらに、それらは人間だけでなくIDEにとっても明らかです。 そしてIDEは、何が何であるかを確実にすでに知っています。

すべての同じネスト関数にコンパイルされますが、可読性は何倍も高くなります。



また、GWTとは異なり、Typescriptはすべてのインデントとコメントを保存してファイルごとにコンパイルされます(WebStormでは-ファイルを保存します)。 Typescriptファイルを表示するとき、Chromeでソースマップを有効にしません-生成されたJSファイルはそれほど変わりません。



また、バックエンドチームでは、誰もがC#とOOPを知っています。 また、Typescriptを使用すると、フロントエンドでそれらを回転させることができます(CSSとレイアウトの知識は重要ではありません。私は企業アプリケーションを繰り返します)。



構造と実装



プロジェクトの構造は次のようになります。





フレームワークはライブラリを「見」、ビジネスはフレームとライブラリを「見」、UIとルーティングはすべてを見る。



この構造と契約は偶然に選択されたものではなく、バックエンドチームの全員が慣れているASP.Netプロジェクトを思い起こさせます。 これをバックエンドからSPAにプルするのは奇妙に思えるかもしれませんが、私にとって開発者の類似点と利便性は明らかです。



標準のTypescriptモジュールのサポートが使用されます(多くのオプションが利用可能ですが):ファイルの先頭でimport(s)と_references.d.tsファイル。 非常に便利なソリューションではありませんが、理解できるIDEです。IDEが理解できれば、他の何かに進むと思います。



Uiレイヤーは、* Facadeクラスを介してのみビジネスにアクセスします。



典型的なファサードは、ほとんどすべてのメソッドがプロミスを返すクラスです。 内部のファサードは、アダプターの課題を担当します。



 ///<reference path="../_references.d.ts"/> module business.security { export class SecurityFacade { loadUsers():ng.IPromise<Array<UserDto>>{} loadGroups():ng.IPromise<Array<GroupDto>>{} renameUser(userId:string, name:string):ng.IPromise<void>{} } }
      
      





オフラインサポートは簡単です。たとえば、SecurityFacade.loadUsersは次のように実装されます。call SecurityAdapter.loadUsers-promiseがエラーを返す場合-IndexedDb SecurityOffineAdapter.loadUsersから読み込み、成功した場合は、成功した結果をIndexedDb-UserOffineAdapter.saveUsersにキャッシュします。 多くの詳細(たとえば、ストレージキュー、インデックス付きDBの制限など)がありますが、大まかに機能します。



 ///<reference path="../_references.d.ts"/> module business.security { export class SecurityFacade { loadUsers():ng.IPromise<Array<UserDto>>{ SecurityAdapter.loadUsers().then(data => { SecurityOffineAdapter.saveUsers(data); return data; }).catch(ex => { if (isOffline()) { return SecurityOffineAdapter.loadUsers();} throw ex }); } } }
      
      





Angular DIを使用しないという決定は、おそらく非標準です。 ビジネスでのAngularの特別な必要性がなくても、Angular Promiseのすべてのタイプの約束(jQueryなど)とさまざまなonSuccess / onErrorをラップする必要があります。

Angular promiseの使用は非常に重要です。最後に自動的に$ applyを呼び出します。 それ以外の場合-自分で$の適用コードを呼び出しでフラッディングし、ダイジェストサイクルにいないことを確認する必要があります。 幸いなことに、$ qは、他のプロバイダーと同様に、Angular DIの外に簡単に移動できます。たとえば、Angular promiseをビジネスで使用する場合などです。

 var $q = angular.element(document.body).injector().get("$q");
      
      





ロード後に送信される前にJsonに保存されたオブジェクトは、アダプターによってチェックされる必要があります。 Typescriptはこれに役立ちません。 したがって、各Dtoクラスには、オブジェクトの各プロパティを内部的にチェックおよびクリーニングし、サブオブジェクトで呼び出すことができるcopyAndVerify静的メソッドがあり、オブジェクトを上位バージョンに「アップグレード」することもできます(たとえば、フィールドの名前を変更してタイプを変更する)。 copyAndVerifyを呼び出すと、コレクション内のオブジェクトごとにアダプターが作成されます。 保存時には、これも必要です。Angularは、シリアル化の前にクリーニングする必要があるオブジェクトにメソッドをハングアップし、Angularディレクティブはタイプを変更できます。たとえば、数値をテキストとして保存します。



UI:SecurityControllerも通常のクラスです。Angularに登録されているだけで、目的のビューも選択します。



 ///<reference path="../_references.d.ts"/> module ui.security { import SecurityFacade = business.shared.SecurityFacade; export class SecurityController { users:Array<User>; constructor(public $scope:IScope, public $window:ng.IWindowService) { new SecurityFacade().loadUsers().then(users=>this.users); //Arrow-functions    var _this=this; } } // @ngInject function SecurityDirective ():ng.IDirective { return { scope: {}, templateUrl: routing.isMobile() ? 'ui/security/Security.html' : 'app/security/SecurityMobile.html', controller:SecurityController , controllerAs: 'securityCtrl' } } angular.module('app').directive('securityDirective', SecurityDirective); }
      
      





1年後



満足の束。 生産ではすでに半年。 すでに約23k LoCであり、同時にプロジェクトは7つのメディアリファクタリング、チームの変更を簡単に乗り越えました。 簡単にメンテナンスできますが、すぐに別の大きな機能を追加する予定です。 最近、彼らはIndexedDbを独自のものに変更しました。 コードレビューは簡単です。



オフラインサポートをいじる必要がありましたが、すべてのiPhone、iPad、Chome、IE10に200Mbを簡単に保存できました。 さらに、SPA(2.5Mb)全体がローカルキャッシュからロードされるため、インターネット接続に関係なくスマートに起動します。



Angularのパフォーマンスは定期的に注意を払うようになりました-ウォッチを削除してイベントに置き換え、ディレクティブとビューのコードを修正します。



ところで、自動テストは、分度器とPageObjectのアプローチを使用してTypescriptで記述されています。



現在、Typescriptは、WebStorm、Visual Studio(2015年に既に使用可能)、およびその他の多くで美しくサポートされています。 今すぐ選択する場合は、AngularではなくReactを選択できます。 また、重いUIコントロールのASP.NetでTypescriptを部分的に適用すると思います。



All Articles