ReactとAngularのどちらがWeb開発に最適なオプションであるかを議論する記事は無数にあります。別のものが必要ですか?
私がこの記事を書いた理由は インクルード 記事 既に 公開 –すばらしいアイデアが含まれていますが、実用的なフロントエンド開発者がニーズを満たすことができるものを決定するために深く掘り下げています。
この記事では、AngularとReactが、哲学が大きく異なるにもかかわらず、同様のフロントエンドの問題を解決することをどのように目指しているか、そしてどちらを選択するかは単に個人的な好みの問題であるかどうかを学びます。それらを比較するために、同じアプリケーションを2回ビルドします。1回はAngularを使用し、もう1回はReactを使用します。
2年前、私は Reactエコシステム 。とりわけ、この記事は、Angularが「以前の発表による死」の犠牲者であったと主張しました。当時、プロジェクトを古いフレームワークで動作させたくない人にとっては、Angularとその他のほとんどすべてのどちらを選択するかは簡単でした。 Angular 1は非推奨になり、Angular2はアルファ版でも利用できませんでした。
振り返ってみると、恐れは多かれ少なかれ正当化されました。 Angular 2は劇的に変化し、最終リリースの直前に大幅な書き直しも行われました。
2年後、Angular 4が登場し、今後の相対的な安定性が約束されています。
そして今それ?
ReactとAngularを比較することは、リンゴとオレンジを比較するようなものだと言う人もいます。 1つは意見を扱うライブラリですが、もう1つは完全なフレームワークです。
もちろん、ほとんどの React開発者 Reactにいくつかのライブラリを追加して、完全なフレームワークにします。さらに、このスタックから生じるワークフローは、Angularとはまだ大きく異なることが多いため、比較可能性は依然として制限されています。
最大の違いは州の行政にあります。 Angularにはデータバインディングが含まれていますが、今日のReactは通常、一方向のデータフローを提供し、不変のデータを処理するためにReduxによって拡張されています。それらはそれ自体で反対のアプローチであり、無数の議論が現在、可変/データ結合が不変/一方向よりも優れているか悪いかを明らかにしています。
Reactはハッキングが簡単なことで有名なので、この比較の目的で、コードスニペットを並べて比較できるようにAngularをかなり厳密に反映するReact構成を構築することにしました。
目立つが、デフォルトではReactにない角度の特徴は次のとおりです。
特徴 | Angularパッケージ | Reactライブラリ |
---|---|---|
データバインディング、依存性注入(DI) | @ angular / core | MobX |
計算されたプロパティ | rxjs | MobX |
コンポーネントベースのルーティング | @ angular / router | Reactルーターv4 |
マテリアルデザインコンポーネント | @角度/素材 | React Toolbox |
コンポーネントを対象としたCSS | @ angular / core | CSSモジュール |
フォームの検証 | @ angular / forms | FormState |
プロジェクトジェネレータ | @ angular / cli | React Scripts TS |
データリンクは、一方向のアプローチよりも開始するのが間違いなく簡単です。もちろん、完全に反対の方向に進んで使用することは可能です 戻ってきた または mobx-state-tree Reactで、そして ngrx Angularで。しかし、それは別の投稿のトピックになります。
パフォーマンスが懸念されるため、Angularのレベルゲッターは各処理で呼び出されるため、問題外です。使用可能です BehaviorSubject から RsJS 、作業を実行します。
Reactで使用することが可能です @computed 同じ目標を達成するMobXから、わずかに優れたAPIを使用します。
依存性注入は、関数型プログラミングと不変性の現在のReactパラダイムに反するため、少し物議を醸しています。結局のところ、ある種の依存性注入は、個別のデータレイヤーアーキテクチャがない場合のデカップリング(したがって、モックとテスト)に役立つため、データバインディング環境ではほとんど不可欠です。
DI(Angularでサポート)のもう1つの利点は、さまざまなストアからさまざまなライフサイクルを持つことができることです。現在のReactパラダイムのほとんどは、さまざまなコンポーネントをマップするある種のグローバルアプリケーション状態を使用していますが、私の経験から、コンポーネントの逆アセンブリでグローバル状態をクリーンアップするときにエラーを導入するのは簡単すぎます。
コンポーネントアセンブリ上に構築された(そしてこのコンポーネントの子が完全に利用できる)ストアを持つことは非常に役立つようであり、その概念は見過ごされがちです。
閉鎖の法則の実例
Angularの箱から出してすぐに使用できますが、MobXでも非常に簡単にプレイできます。
コンポーネントベースのルーティングにより、コンポーネントは、大規模なグローバルルーター構成を使用する代わりに、独自の子ルートを管理できます。このアプローチはついにreact-router
に到達しましたバージョン4では。
いくつかのハイエンドコンポーネントから始めることは常に良いことであり、マテリアルデザインは、Google以外のプロジェクトであっても、広く受け入れられているデフォルトオプションのようなものになっています。
私は意図的に選択しました React Toolbox 推奨について マテリアルUI 、マテリアルUIには深刻な自白があるため パフォーマンスの問題 次のバージョンで解決する予定のCSS-in-lineを使用します。
さらに、 PostCSS / cssnext React Toolboxで使用され、とにかくSass / LESSに取って代わり始めています。
CSSクラスはグローバル変数のようなものです。競合を回避するためにCSSを編成する方法は多数あります( 良い )が、開発者を必要とせずにこれらの競合を回避するためにCSSを処理するのに役立つライブラリの使用には明らかな傾向があります フロントエンド 精巧なCSSネーミングシステムを作成します。
フォームの検証は重要で、広く使用されている機能です。コードの繰り返しやエラーを避けるために、それらをライブラリでカバーすることをお勧めします。
プロジェクト用のCLIジェネレーターがあると、GitHubからボイラーシートのクローンを作成するよりも少しだけ便利です。
したがって、ReactとAngularで同じアプリケーションを作成します。見事なものは何もありません。誰でも共通のページにメッセージを投稿できるShoutboardだけです。
ここでアプリケーションをテストできます。
すべてのソースコードが必要な場合は、GitHubから入手できます。
ReactアプリケーションにもTypeScriptを使用していることに気付くでしょう。 TypeScriptでの型チェックの利点は明らかです。そして今、インポートのより良い処理で、非同期/待機と残りの伝播は最終的にTypeScript 2に到達し、Babel / ES7 /を残しました フロー ほこりの中で。
また、追加します Apolloクライアント GraphQLを使用したいので、両方に。つまり、RESTは素晴らしいのですが、10年ほどで古くなります。
まず、両方のアプリのエントリポイントを見てみましょう。
Angular
const appRoutes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'posts', component: PostsComponent }, { path: 'form', component: FormComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' } ] @NgModule({ declarations: [ AppComponent, PostsComponent, HomeComponent, FormComponent, ], imports: [ BrowserModule, RouterModule.forRoot(appRoutes), ApolloModule.forRoot(provideClient), FormsModule, ReactiveFormsModule, HttpModule, BrowserAnimationsModule, MdInputModule, MdSelectModule, MdButtonModule, MdCardModule, MdIconModule ], providers: [ AppService ], bootstrap: [AppComponent] })
@Injectable() export class AppService { username = 'Mr. User' }
基本的に、アプリケーションで使用するすべてのコンポーネントは、宣言に移動する必要があります。すべてのサードパーティ書店から輸入品、すべてのグローバルストアからサプライヤーへ。子コンポーネントはこれらすべてにアクセスでき、ローカルのものを追加する機会があります。
React
const appStore = AppStore.getInstance() const routerStore = RouterStore.getInstance() const rootStores = { appStore, routerStore } ReactDOM.render( , document.getElementById('root') )
このコンポーネントは、MobXでの依存性注入に使用されます。 Reactコンポーネントが後でそれらを注入できるように、ストアをコンテキストに保存します。はい、Reactコンテキストは(おそらく)使用できます 安全に 。
モジュール宣言がないため、Reactバージョンは少し短くなっています。通常はインポートするだけで、準備ができています。この種のハード依存関係は(テストでは)望ましくない場合があるため、グローバルシングルトンストアではこれを使用する必要がありました GoF パターン 古い10年:
export class AppStore { static instance: AppStore static getInstance() (AppStore.instance = new AppStore()) @observable username = 'Mr. User' }
Angularのルーターは注入可能であるため、コンポーネントだけでなく、どこからでも使用できます。反応で同じことを達成するために、私たちはパッケージを使用します mobx-react-router そして、routerStore
を注入します。
概要: 両方のアプリのブートストラップは非常に簡単です。 Reactには、モジュールの代わりにインポートのみを使用する方が簡単であるという利点がありますが、後で説明するように、これらのモジュールは非常に便利です。手動でセミフォールを作成するのは少し面倒です。ルーティングステートメントの構文に関しては、JSONとJSXは好みの問題です。
したがって、ルートを変更する場合は2つあります。宣言的には、
要素を使用し、必須として、ルーティング(したがってロケーション)APIを直接呼び出します。
Angular
Home Posts {this.props.children}
React Routerは、activeClassName
を使用してリンクアクティブクラスを設定することもできます。
ここでは、CSSモジュールコンパイラによって一意にされているため、クラス名を直接指定することはできません。また、style
ヘルパーを使用する必要があります。これについては後で説明します。
上で見たように、ReactRouterは要素内の要素を使用します。要素は単に現在のパスをラップしてマウントするため、現在のコンポーネントのサブパスは単純にthis.props.children
であることを意味します。だからそれも構成可能です。
export class FormStore { routerStore: RouterStore constructor() { this.routerStore = RouterStore.getInstance() } goBack = () => { this.routerStore.history.push('/posts') } }
パッケージmobx-router-store
また、簡単な注入とナビゲーションが可能です。
概要: 両方のルーティングアプローチは非常に類似しています。 Angularの方が直感的であるように見えますが、ReactRouterは作成が少し簡単です。
データ層をプレゼンテーション層から分離することは、すでに有益であることが示されています。 DIで達成しようとしているのは、データレイヤーのコンポーネント(ここではモデル/ストア/サービスと呼ばれます)をビジュアルコンポーネントのライフサイクルに従わせることです。したがって、グローバルステータスに触れることなく、これらのコンポーネントの1つ以上のインスタンスを作成できます。 。また、サポートされているデータと表示レイヤーを組み合わせて使用できるようにする必要があります。
この記事の例は非常に単純なので、すべてのDIが過剰に見えるかもしれませんが、アプリケーションが大きくなるにつれて役立ちます。
Angular
@Injectable() export class HomeService { message = 'Welcome to home page' counter = 0 increment() { this.counter++ } }
したがって、任意のクラスを作成でき@inyectable
、そのプロパティとメソッドをコンポーネントで使用できるようにします。
@Component({ selector: 'app-home', templateUrl: './home.component.html', providers: [ HomeService ] }) export class HomeComponent { constructor( public homeService: HomeService, public appService: AppService, ) { } }
HomeService
を登録するa providers
コンポーネントのうち、このコンポーネントでのみ使用できるようにします。今はハーフバグではありませんが、コンポーネントの各インスタンスは、コンポーネントのアセンブリで新しいコピーを受け取ります。これは、以前の使用からの古いデータがないことを意味します。
対照的に、AppService
app.module
に登録されています(上記を参照)、したがって、それは半休閑であり、アプリケーションの期間はありますが、すべてのコンポーネントで同じままです。コンポーネントサービスのライフサイクルを制御できることは非常に便利な概念ですが、ほとんど評価されていません。
DIは、TypeScriptタイプで識別されるコンポーネントのコンストラクターにサービスインスタンスを割り当てることで機能します。また、キーワードpublic
パラメータを this
に自動的に割り当てるので、これらの退屈な行を入力する必要はありませんthis.homeService = homeService
。
Dashboard
Clicks since last visit: {{homeService.counter}} Click!
Angularのテンプレート構文は確かに非常にエレガントです。双方向のデータバインディングのように機能する[()]
ショートカットが好きですが、内部的には、実際にはバインディング+イベント属性です。私たちのサービスのライフサイクルによって決定されるように、homeService.counter
/home
から離れるたびに再起動しますが、appService.username
残り、どこからでもアクセスできます。
React
import { observable } from 'mobx' export class HomeStore { @observable counter = 0 increment = () => { this.counter++ } }
MobXでは、デコレータを追加する必要があります@observable
観察可能にしたいすべてのプロパティに。
@observer export class Home extends React.Component { homeStore: HomeStore componentWillMount() { this.homeStore = new HomeStore() } render() { return } }
ライフサイクルを適切に管理するには、Angularの例よりも少し多くの作業を行う必要があります。 HomeComponent
をラップします Provider
の新しいインスタンスを受け取るHomeStore
の内部各アセンブリで。
interface HomeComponentProps { appStore?: AppStore, homeStore?: HomeStore } @inject('appStore', 'homeStore') @observer export class HomeComponent extends React.Component { render() { const { homeStore, appStore } = this.props return Dashboard
Clicks since last visit: {homeStore.counter} Click! } }
HomeComponent
デコレータを使用します@observer
@observable
プロパティへの変更をリッスンします。
これに関する内部メカニズムはかなりクールなので、ここで簡単に説明しましょう。デコレータ@observable
getterとsetterを使用してオブジェクトのプロパティをオーバーライドし、呼び出しをインターセプトできるようにします。拡張コンポーネントのレンダリング関数が呼び出されると、@observador
が呼び出され、getterプロパティが呼び出され、呼び出し元のコンポーネントへの参照が保持されます。
次に、setterが呼び出されて値が変更されると、最後のレンダリングでプロパティを使用したコンポーネントのレンダリング関数が呼び出されます。これで、どのプロパティがどこで使用されているかに関するデータが更新され、サイクル全体を最初からやり直すことができます。
非常にシンプルなメカニズムであり、パフォーマンスも非常に優れています。より詳細な説明 ここに 。
デコレータ@inyectar 'se utiliza para inyectar instancias
appStore y
homeStore en los accesorios de
HomeComponent . En este punto, cada una de esas tiendas tiene un ciclo de vida diferente.
appStore es el mismo durante la vida de la aplicación, pero
homeStore`は、パス「/ home」への各ナビゲーションで新しく作成されます。
これの利点は、すべてのストアがグローバルである場合のように、プロパティを手動でクリアする必要がないことです。これは、パスが毎回完全に異なるデータを含む「詳細」ページである場合に苦痛です。
概要: ベンダーのライフサイクル管理はAngularのDIに固有の機能であるため、もちろん、そこで実現する方が簡単です。 Reactバージョンも使用可能ですが、より多くの定型文が含まれます。
React
これについてReactから始めましょう。より直接的な解決策があります。
import { observable, computed, action } from 'mobx' export class HomeStore { import { observable, computed, action } from 'mobx' export class HomeStore { @observable counter = 0 increment = () => { this.counter++ } @computed get counterMessage() { console.log('recompute counterMessage!') return `${this.counter} ${this.counter === 1 ? 'click' : 'clicks'} since last visit` } }
したがって、counter
にバインドする計算プロパティがあります。正しく複数形にされたメッセージを返します。 counterMessage
の結果 counter
の場合にのみ、キャッシュおよび再計算されます。変化します。
{homeStore.counterMessage} Click!
次に、JSXテンプレートのプロパティ(およびincrement
メソッド)を参照します。入力フィールドは値にバインドすることで制御され、 `appStore 'メソッドがユーザーイベントを処理できるようにします。
Angular
Angularで同じ効果を達成するには、もう少し独創的である必要があります。
import { Injectable } from '@angular/core' import { BehaviorSubject } from 'rxjs/BehaviorSubject' @Injectable() export class HomeService { message = 'Welcome to home page' counterSubject = new BehaviorSubject(0) // Computed property can serve as basis for further computed properties counterMessage = new BehaviorSubject('') constructor() { // Manually subscribe to each subject that couterMessage depends on this.counterSubject.subscribe(this.recomputeCounterMessage) } // Needs to have bound this private recomputeCounterMessage = (x) => { console.log('recompute counterMessage!') this.counterMessage.next(`${x} ${x === 1 ? 'click' : 'clicks'} since last visit`) } increment() { this.counterSubject.next(this.counterSubject.getValue() + 1) } }
計算されたプロパティの基礎となるすべての値をBehaviorSubject
として定義する必要があります。計算されたプロパティ自体もBehaviorSubject
です。これは、計算されたプロパティが別の計算されたプロパティへの入力として機能するためです。
もちろん、RxJS
できる はるかに これだけではありませんが、それはまったく別の記事のトピックになります。マイナーな欠点は、計算されたプロパティに対するRxJSのこの些細な使用は、リアクティブな例よりも少し冗長であり、サブスクリプションを手動で管理する必要があることです(ここのコンストラクターのように)。
{ async} Click!
|asíncrona
を使用してRxJSサブジェクトを参照する方法に注意してください。 。これはいい感じで、コンポーネントをサブスクライブする必要があるよりもはるかに短いです。コンポーネントinput
ディレクティブ[(ngModel)]
によって駆動されます。奇妙に見えますが、実際にはかなりスタイリッシュです。値データをappService.username
にバインドするための単なる構文糖衣およびユーザー入力イベントの自動割り当て値。
概要: 計算されたプロパティは、Angular / RxJSよりもReact / MobXで実装する方が簡単ですが、RxJSは、後で理解できるいくつかのより便利なFRP機能を提供できます。
テンプレートがどのようにスタックするかを示すために、メッセージのリストを表示するメッセージコンポーネントを使用します。
Angular
@Component({ selector: 'app-posts', templateUrl: './posts.component.html', styleUrls: ['./posts.component.css'], providers: [ PostsService ] }) export class PostsComponent implements OnInit { constructor( public postsService: PostsService, public appService: AppService ) { } ngOnInit() { this.postsService.initializePosts() } }
このコンポーネントは、HTML、CSS、および挿入されたサービスのみを接続し、初期化時にAPIメッセージをロードする関数も呼び出します。 AppService
はアプリケーションモジュールで定義されたセミエラーですが、 PostsService
これは一時的なものであり、コンポーネントが作成されるたびに新しいインスタンスが作成されます。このコンポーネントから参照されるCSSは、このコンポーネントにスコープされます。つまり、コンテンツはコンポーネントの外部に影響を与えることはできません。
add Hello {{appService.username}}
{{post.title}} {{post.name}} {{post.message}}
HTMLテンプレートでは、主にAngularMaterialコンポーネントを参照します。それらを利用可能にするには、それらをインポートに含める必要がありましたapp.module
(上記を参照)。ディレクティブ*ngFor
コンポーネントを繰り返すために使用されますmd-card
ポジションごとに。
ローカルCSS:
.mat-card { margin-bottom: 1rem; }
ローカルCSSは、コンポーネントmd-card
に存在するクラスの1つのみを増やします。
グローバルCSS:
.float-right { float: right; }
このクラスはグローバルファイルで定義されていますstyle.css
すべてのコンポーネントで使用できるようにします。標準形式class =' float-right '
で参照できます。
コンパイルされたCSS:
アートのデザインの原則は何ですか
.float-right { float: right; } .mat-card[_ngcontent-c1] { margin-bottom: 1rem; }
コンパイルされたCSSでは、ローカルCSSが属性セレクター[_ngcontent-c1]
によってレンダリングされたコンポーネントに区切られていることがわかります。レンダリングされたすべてのAngularコンポーネントには、CSSスコープの目的でこのような生成されたクラスがあります。
このメカニズムの利点は、クラスを通常どおり参照でき、スコープが「内部」で処理されることです。
React
import * as style from './posts.css' import * as appStyle from '../app.css' @observer export class Posts extends React.Component { postsStore: PostsStore componentWillMount() { this.postsStore = new PostsStore() this.postsStore.initializePosts() } render() { return } }
Reactでも、Provider
を使用する必要があります。 PostsStoreの依存関係を「一時的」にするため。 style
という名前のCSSスタイルもインポートします。およびappStyle
、JSXでこれらのCSSファイルのクラスを使用できるようにします。
interface PostsComponentProps { appStore?: AppStore, postsStore?: PostsStore } @inject('appStore', 'postsStore') @observer export class PostsComponent extends React.Component { render() { const { postsStore, appStore } = this.props return Hello {appStore.username}
{postsStore.posts.map(post => {post.message} )} } }
当然のことながら、JSXはAngularのHTMLテンプレートよりもJavaScriptのように感じます。これは、好みに応じて良いことも悪いこともあります。ディレクティブ*ngFor
の代わりに、構成 map
を使用します。投稿を繰り返し処理します。
AngularはTypeScriptを最も促進するフレームワークかもしれませんが、実際にはTypScriptが本当に輝いているのはJSXです。 CSSモジュール(上記でインポート)を追加すると、テンプレートのコーディングが実際にコードzenに変わります。すべてがチェックされます。コンポーネント、属性、さらにはCSSクラス(appStyle.floatRight
および style.messageCard
、以下を参照)。そしてもちろん、JSXの薄い性質は、Angularのテンプレートよりも少し多くのコンポーネントとチャンクへの分割を促進します。
ローカルCSS:
.messageCard { margin-bottom: 1rem; }
グローバルCSS:
.floatRight { float: right; }
コンパイルされたCSS:
.floatRight__qItBM { float: right; } .messageCard__1Dt_9 { margin-bottom: 1rem; }
ご覧のとおり、ローダーfor CSSモジュールは、各CSSクラスにランダムな接尾辞を付けて、一意性を保証します。競合を回避する簡単な方法。次に、クラスはWebパッケージからインポートされたオブジェクトを介して参照されます。これの考えられる欠点は、Angularの例で行ったように、クラスを使用してCSSを作成して拡張できないことです。一方、これはスタイルを正しくカプセル化する必要があるため、本当に良いことです。
概要: 個人的には、主にコードの完了と制御サポートのタイプのために、AngularテンプレートよりもJSXの方が少し好きです。それは本当にキラー機能です。 AngularにはAOTコンパイラが搭載されており、これもいくつかのことを検出できます。コードの完了は、そこにあるものの約半分でも機能しますが、JSX / TypeScriptほど完全ではありません。
そのため、このアプリケーションのデータを格納するためにGraphQLを使用することにしました。 GraphQLバックエンドを作成する最も簡単な方法の1つは、GraphcoolなどのBaaSを使用することです。それが私たちがしたことです。基本的には、モデルと属性を定義するだけで、CRUDを使用できます。
共通コード
GraphQLに関連するコードの一部は両方の実装で100%同じであるため、2回繰り返すことはありません。
const PostsQuery = gql` query PostsQuery { allPosts(orderBy: createdAt_DESC, first: 5) { id, name, title, message } } `
GraphQLは、従来のRESTfulエンドポイントと比較してより豊富な機能セットを提供することを目的としたクエリ言語です。この特定のクエリを分析します。
PostsQuery
後で参照するためのこのクエリの単なる名前です。任意の名前を付けることができます。allPosts
これは最も重要な部分です。これは、「Post」パターンですべてのレコードをクエリする関数を指します。この名前はGraphcoolによって作成されました。orderBy
およびfirst
allPosts
関数のパラメーターです。 createdAt
Post
の1つですモデル属性。 first: 5
クエリの最初の5つの結果のみを返すことを意味します。id
、name
、title
、およびmessage
モデルの属性ですPost
結果に含めたいということです。他の属性はフィルタリングされます。すでにご覧のとおり、非常に強力です。を見てみましょう このページ GraphQLクエリに慣れるため。
interface Post { id: string name: string title: string message: string } interface PostsQueryResult { allPosts: Array }
はい、TypeScriptの良き市民として、GraphQL結果のインターフェースを作成します。
Angular
@Injectable() export class PostsService { posts = [] constructor(private apollo: Apollo) { } initializePosts() { this.apollo.query({ query: PostsQuery, fetchPolicy: 'network-only' }).subscribe(({ data }) => { this.posts = data.allPosts }) } }
GraphQLクエリは監視可能なRxJSであり、サブスクライブします。それは約束のように少し機能しますが、そうではないので、async/await
では運がありません。もちろんまだあります お約束します 、しかしそれはとにかくAngularパスではないようです。設定fetchPolicy: 'network-only'
この場合、データをキャッシュしたくないので、毎回再フェッチします。
React
export class PostsStore { appStore: AppStore @observable posts: Array = [] constructor() { this.appStore = AppStore.getInstance() } async initializePosts() { const result = await this.appStore.apolloClient.query({ query: PostsQuery, fetchPolicy: 'network-only' }) this.posts = result.data.allPosts } }
Reactのバージョンはほとんど同じですが、apolloClient
ここではpromiseを使用しているので、 async / await
構文を利用できます。 Reactには、GraphQLクエリを「書き込む」だけのアプローチが他にもあります。 高次コンポーネント 、しかし、データレイヤーとプレゼンテーションが混ざりすぎて少し多すぎることがわかりました。
要約すれば: RxJSサブスクライブと非同期/待機の考え方は実際には同じです。
共通コード
繰り返しますが、いくつかの関連するGraphQLコード:
const AddPostMutation = gql` mutation AddPostMutation($name: String!, $title: String!, $message: String!) { createPost( name: $name, title: $title, message: $message ) { id } } `
ミューテーションの目的は、レコードを作成または更新することです。したがって、データを渡す方法であるため、ミューテーションを使用していくつかの変数を宣言することは有益です。したがって、変数name
、 title
およびmessage
、String
と記述されます。これは、このミューテーションを呼び出すたびに入力する必要があります。関数createPost
も、Graphcoolによって定義されています。モデルキーPost
出力ミューテーション変数の値があり、 id
が必要です。新しく作成された投稿のが返送されます。
Angular
@Injectable() export class FormService { constructor( private apollo: Apollo, private router: Router, private appService: AppService ) { } addPost(value) { this.apollo.mutate({ mutation: AddPostMutation, variables: { name: this.appService.username, title: value.title, message: value.message } }).subscribe(({ data }) => { this.router.navigate(['/posts']) }, (error) => { console.log('there was an error sending the query', error) }) } }
apollo.mutate
を呼び出すときは、呼び出すミューテーションと変数も指定する必要があります。コールバック関数で結果を取得しますsubscribe
挿入された「ローテーター」を使用して、メーリングリストに戻ります。
React
export class FormStore { constructor() { this.appStore = AppStore.getInstance() this.routerStore = RouterStore.getInstance() this.postFormState = new PostFormState() } submit = async () => { await this.postFormState.form.validate() if (this.postFormState.form.error) return const result = await this.appStore.apolloClient.mutate( { mutation: AddPostMutation, variables: { name: this.appStore.username, title: this.postFormState.title.value, message: this.postFormState.message.value } } ) this.goBack() } goBack = () => { this.routerStore.history.push('/posts') } }
上記と非常に似ていますが、より「手動」の依存性注入とasync/await
の使用が異なります。
要約すれば: ここでも、大きな違いはありません。 subscribe vs async / awaitは基本的にすべてが異なります。
このアプリケーションのフォームを使用して、次の目標を達成したいと考えています。
React
export const check = (validator, message, options) => (value) => (!validator(value, options) && message) export const checkRequired = (msg: string) => check(nonEmpty, msg) export class PostFormState { title = new FieldState('').validators( checkRequired('Title is required'), check(isLength, 'Title must be at least 4 characters long.', { min: 4 }), check(isLength, 'Title cannot be more than 24 characters long.', { max: 24 }), ) message = new FieldState('').validators( checkRequired('Message cannot be blank.'), check(isLength, 'Message is too short, minimum is 50 characters.', { min: 50 }), check(isLength, 'Message is too long, maximum is 1000 characters.', { max: 1000 }), ) form = new FormState({ title: this.title, message: this.message }) }
したがって、書店 formstate これは次のように機能します。フォームの各フィールドに対して、FieldState
を定義します。渡されるパラメータは初期値です。 validators
値が有効な場合は「false」を返し、値が無効な場合は検証メッセージを返す関数を取ります。 check' y
checkRequired`関数を使用すると、すべてが非常に宣言的に見える場合があります。
フォーム全体を検証するには、これらのフィールドをインスタンスFormState
でラップすることも有益です。これにより、追加の有効性が提供されます。
@inject('appStore', 'formStore') @observer export class FormComponent extends React.Component { render() { const { appStore, formStore } = this.props const { postFormState } = formStore return Create a new post
You are now posting as {appStore.username}
インスタンスFormState
プロパティを提供しますvalue
、onChange
およびerror
は、任意のフロントエンドコンポーネントで簡単に使用できます。
} }
いつform.hasError
true
の場合、ボタンは無効のままにします。 [送信]ボタンは、フォームを上記のGraphQLミューテーションに送信します。
Angular
Angularでは、FormService
を使用します。およびFormBuilder
は、パッケージの一部です@angular/forms
。
@Component({ selector: 'app-form', templateUrl: './form.component.html', providers: [ FormService ] }) export class FormComponent { postForm: FormGroup validationMessages = { 'title': { 'required': 'Title is required.', 'minlength': 'Title must be at least 4 characters long.', 'maxlength': 'Title cannot be more than 24 characters long.' }, 'message': { 'required': 'Message cannot be blank.', 'minlength': 'Message is too short, minimum is 50 characters', 'maxlength': 'Message is too long, maximum is 1000 characters' } }
まず、検証メッセージを定義しましょう。
constructor( private router: Router, private formService: FormService, public appService: AppService, private fb: FormBuilder, ) { this.createForm() } createForm() { this.postForm = this.fb.group({ title: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(24)] ], message: ['', [Validators.required, Validators.minLength(50), Validators.maxLength(1000)] ], }) }
FormBuilder
を使用すると、Reactの例よりもさらに簡潔に、フォーム構造を非常に簡単に作成できます。
get validationErrors() { const errors = {} Object.keys(this.postForm.controls).forEach(key => { errors[key] = '' const control = this.postForm.controls[key] if (control && !control.valid) { const messages = this.validationMessages[key] Object.keys(control.errors).forEach(error => { errors[key] += messages[error] + ' ' }) } }) return errors }
バインディング検証メッセージを適切な場所に取得するには、いくつかの処理を行う必要があります。このコードは、いくつかの小さな変更を加えて、公式ドキュメントから取得されています。基本的に、FormServiceでは、フィールドはバリデーター名で識別されるアクティブなエラーのみへの参照を保持するため、必要なメッセージを影響を受けるフィールドに手動で照合する必要があります。これは完全に不便ではありません。たとえば、国際化に適しています。
onSubmit({ value, valid }) { if (!valid) { return } this.formService.addPost(value) } onCancel() { this.router.navigate(['/posts']) } }
繰り返しになりますが、フォームが有効な場合、データをGraphQLミューテーションに送信できます。
Create a new post
You are now posting as {{appService.username}}
{{validationErrors['title']}}
{{validationErrors['message']}}
Cancel Submit
最も重要なことは、FormBuilderで作成したformGroupを参照することです。これは[formGroup] = 'postForm'
割り当てです。フォーム内のフィールドは、formControlName
プロパティを介してフォームモデルにリンクされます。ここでも、フォームが無効な場合は[送信]ボタンを無効にします。ここでは、ダーティでないフォームが無効になる可能性があるため、ダーティチェックも追加する必要があります。ボタンの初期状態を「有効」にする必要があります。
要約すれば: ReactとAngularのフォームに対するこのアプローチは、検証とテンプレートの面でまったく異なります。 Angularアプローチには、直接リンクではなく、もう少し「魔法」が含まれますが、一方で、より包括的です。
ああ、もう一つ。プロダクションはJSパッケージのサイズを縮小し、アプリケーションジェネレーターのデフォルト設定を使用しました。特にReactのTree ShakingとAngularのAOTビルドです。
さて、ここではそれほど驚きはありません。 Angularは常に最もかさばっています。
gzipを使用すると、サイズはそれぞれ275kbと127kbに下がります。
これらは基本的にすべてベンダーライブラリであることに注意してください。比較すると、実際のアプリケーションコードの量は最小限ですが、実際のアプリケーションには当てはまりません。そこでは、比率はおそらく1:4よりも1:2に近いでしょう。また、Reactに多くのサードパーティライブラリを含め始めると、パッケージサイズも非常に急速に大きくなる傾向があります。
そのため、(再び!)AngularとReactのどちらがWeb開発に適しているかについて明確な答えを出すことができなかったようです。
ReactとAngularの開発ワークフローは、Reactの使用を選択したライブラリに応じて、非常に類似している可能性があることがわかりました。したがって、それは主に個人的な好みの問題です。
既製のスタック、強力な依存性注入、およびRxJSのいくつかの機能を使い切る計画が好きな場合は、Angularを選択してください。
JSXのシンプルさのように、ゲームをプレイして独自のスタックを構築し、よりシンプルな計算可能なプロパティを好む場合は、React / MobXを選択します。
繰り返しになりますが、この記事からアプリケーションの完全なソースコードを入手できます ここに Y ここに 。
または、より大きな例を好む場合、およびRealWorldから:
React / MobXを使用したプログラミングは、実際にはReact / ReduxよりもAngularに似ています。テンプレートと依存関係の処理にはいくつかの顕著な違いがありますが、それらは同じパラダイムを持っています 可変/データバインディング 。
React / Reduxとそのパラダイム 不変/単方向 それは完全に異なる獣です。
Reduxライブラリの小さなフットプリントにだまされないでください。小さいかもしれませんが、とにかくフレームワークです。今日のReduxのベストプラクティスのほとんどは、次のようなredux互換ライブラリの使用に焦点を合わせています。 Redux Saga 非同期コードおよびデータ検索の場合、 Reduxフォーム フォーム管理用、 再選択 記憶されたセレクター(計算されたRedux値)の場合。 Y 再構成 とりわけ、より細かいライフサイクル管理のために。また、Reduxコミュニティには Immutable.js に ラムダ または lodash / fp 、単純なJSオブジェクトを変換する代わりに処理します。
現代のReduxの良い例はよく知られています ボイラープレートに反応する 。これは手ごわい開発スタックですが、見てみると、これまでこの投稿で見たものとは非常に大きく異なります。
Angularは、JavaScriptコミュニティのより声高な部分から少し不公平な扱いを受けているように感じます。それに不満を表明している多くの人々は、おそらく古いAngularJSと今日のAngularの間で起こった大きな変化に感謝していません。私の意見では、それが1〜2年前に登場したとしたら、世界を席巻する非常にクリーンで生産的なフレームです。
ただし、Angularは、特に企業の世界で、大規模なチームと標準化と長期的なサポートの必要性により、確固たる足場を築いています。言い換えれば、Angularは、Googleのエンジニアが、ウェブ開発を行うべきだと考える方法です。
MobXについても、同様の評価が適用されます。本当にかっこいいですが、あまり評価されていません。
結論:ReactとAngularのどちらかを選択する前に、まずプログラミングパラダイムを選択してください。
可変/データバインディング または 不変/単方向 、それが本当の問題のようです。