apeescape2.com
  • メイン
  • 製品の担当者とチーム
  • 収益と成長
  • デザイナーライフ
  • Kpiと分析
Webフロントエンド

Angular vs. React:Web開発に最適なのはどれですか?

ReactとAngularのどちらがWeb開発に最適なオプションであるかを議論する記事は無数にあります。別のものが必要ですか?

私がこの記事を書いた理由は インクルード 記事 既に 公開 –すばらしいアイデアが含まれていますが、実用的なフロントエンド開発者がニーズを満たすことができるものを決定するために深く掘り下げています。

Angular vs. React:フレームワーク形式を選択しますか、それとも書店をいじりますか?



この記事では、AngularとReactが、哲学が大きく異なるにもかかわらず、同様のフロントエンドの問題を解決することをどのように目指しているか、そしてどちらを選択するかは単に個人的な好みの問題であるかどうかを学びます。それらを比較するために、同じアプリケーションを2回ビルドします。1回はAngularを使用し、もう1回はReactを使用します。

Angularのタイムリーでない発表

2年前、私は Reactエコシステム 。とりわけ、この記事は、Angularが「以前の発表による死」の犠牲者であったと主張しました。当時、プロジェクトを古いフレームワークで動作させたくない人にとっては、Angularとその他のほとんどすべてのどちらを選択するかは簡単でした。 Angular 1は非推奨になり、Angular2はアルファ版でも利用できませんでした。

振り返ってみると、恐れは多かれ少なかれ正当化されました。 Angular 2は劇的に変化し、最終リリースの直前に大幅な書き直しも行われました。

2年後、Angular 4が登場し、今後の相対的な安定性が約束されています。

そして今それ?

Angular vs. React:リンゴとオレンジの比較

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を処理するのに役立つライブラリの使用には明らかな傾向があります フロントエンド 精巧なCSSネーミングシステムを作成します。

フォームの検証

フォームの検証は重要で、広く使用されている機能です。コードの繰り返しやエラーを避けるために、それらをライブラリでカバーすることをお勧めします。

プロジェクトジェネレータ

プロジェクト用のCLIジェネレーターがあると、GitHubからボイラーシートのクローンを作成するよりも少しだけ便利です。

同じアプリ、2回ビルド

したがって、ReactとAngularで同じアプリケーションを作成します。見事なものは何もありません。誰でも共通のページにメッセージを投稿できるShoutboardだけです。

ここでアプリケーションをテストできます。

  • シャウトボードアンギュラー
  • シャウトボード反応

ShoutboardアプリenAngular vs. en React

すべてのソースコードが必要な場合は、GitHubから入手できます。

  • ShoutboardAngularソース
  • ShoutboardReactソース

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機能を提供できます。

テンプレートとCSS

テンプレートがどのようにスタックするかを示すために、メッセージのリストを表示するメッセージコンポーネントを使用します。

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を使用することにしました。 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-データの保存

共通コード

繰り返しますが、いくつかの関連する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ビルドです。

  • 角度:1200 KB
  • React:300 KB

さて、ここではそれほど驚きはありません。 Angularは常に最もかさばっています。

gzipを使用すると、サイズはそれぞれ275kbと127kbに下がります。

これらは基本的にすべてベンダーライブラリであることに注意してください。比較すると、実際のアプリケーションコードの量は最小限ですが、実際のアプリケーションには当てはまりません。そこでは、比率はおそらく1:4よりも1:2に近いでしょう。また、Reactに多くのサードパーティライブラリを含め始めると、パッケージサイズも非常に急速に大きくなる傾向があります。

ライブラリの柔軟性とフレームワークの堅牢性

そのため、(再び!)AngularとReactのどちらがWeb開発に適しているかについて明確な答えを出すことができなかったようです。

ReactとAngularの開発ワークフローは、Reactの使用を選択したライブラリに応じて、非常に類似している可能性があることがわかりました。したがって、それは主に個人的な好みの問題です。

既製のスタック、強力な依存性注入、およびRxJSのいくつかの機能を使い切る計画が好きな場合は、Angularを選択してください。

JSXのシンプルさのように、ゲームをプレイして独自のスタックを構築し、よりシンプルな計算可能なプロパティを好む場合は、React / MobXを選択します。

繰り返しになりますが、この記事からアプリケーションの完全なソースコードを入手できます ここに Y ここに 。

または、より大きな例を好む場合、およびRealWorldから:

  • RealWorld Angular 4+
  • RealWorld React / MobX

まず、プログラミングパラダイムを選択します

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のどちらかを選択する前に、まずプログラミングパラダイムを選択してください。

可変/データバインディング または 不変/単方向 、それが本当の問題のようです。

アメリカの開発者RachellCalhounが5回目のApeeScape奨学金を獲得

その他

アメリカの開発者RachellCalhounが5回目のApeeScape奨学金を獲得
WebAssembly / Rustチュートリアル:ピッチパーフェクトなオーディオ処理

WebAssembly / Rustチュートリアル:ピッチパーフェクトなオーディオ処理

Webフロントエンド

人気の投稿
.NETCore-ワイルドでオープンソース化。マイクロソフト、どうしてそんなに時間がかかったの?
.NETCore-ワイルドでオープンソース化。マイクロソフト、どうしてそんなに時間がかかったの?
パーム油への投資家向けガイド
パーム油への投資家向けガイド
B2B UX –一般的な障害と達成可能なソリューション
B2B UX –一般的な障害と達成可能なソリューション
フレーマーチュートリアル:プロトタイプを改善するための7つの簡単なマイクロインタラクション
フレーマーチュートリアル:プロトタイプを改善するための7つの簡単なマイクロインタラクション
フリーランスのファイナンスコンサルタントが大企業をどのように打ち負かしているか
フリーランスのファイナンスコンサルタントが大企業をどのように打ち負かしているか
 
成功する市場開拓戦略を作成する方法
成功する市場開拓戦略を作成する方法
ブロックチェーンID管理:データセキュリティ革命の火付け役
ブロックチェーンID管理:データセキュリティ革命の火付け役
より軽く、より速く-Svelteフレームワークのガイド
より軽く、より速く-Svelteフレームワークのガイド
UXポートフォリオのヒントとベストプラクティス
UXポートフォリオのヒントとベストプラクティス
フリーランサーの個人情報の盗難:それは私に起こりました—これがあなたが知っておくべきことです
フリーランサーの個人情報の盗難:それは私に起こりました—これがあなたが知っておくべきことです
人気の投稿
  • aws認定ソリューションアーキテクト試験の青写真
  • 不和ボットは何をしますか
  • ソフトウェア工学におけるコスト見積もり
  • scorpとaccorpの違い
  • pythonクラスに属性を追加
カテゴリー
プロジェクト管理 バックエンド アジャイル デザイナーライフ 製品の担当者とチーム 投資家と資金調達 設計プロセス 収益性と効率性 プロセスとツール ライフスタイル

© 2021 | 全著作権所有

apeescape2.com