Chapter 05

1章.4 よりよいメンタルモデル形成:特徴を知る1

Satoshi Takeda
Satoshi Takeda
2021.09.30に更新

ここでは React, Angular の特徴を少しだけおさらいする程度です。新しい発見が多くなさそうですが、2 つをまたいだ場合に共通して知っておくべきこと、後半の内容とも関連することにも触れていきます。

DOM と変更のアプローチ

React と仮想 DOM

React が登場したときから仮想 DOM の存在とその差分検知による DOM の更新が開発者の魂を震わせてきたのだと認識しています。

前述のとおり DOM 構築のためにメンタルモデルを補助する JSX と、差分検知のしくみである reconciler によって仮想 DOM の差分検知(および実 DOM へのコミット)が実現されています。ユーザーである開発者が任意のタイミング・任意の場所で render する必要はなく、仮想 DOM と呼ばれるオブジェクトツリーの変更は React がヒューリスティクスによって差分として検知します。差分だけが必ず render されるというわけではないということは気にかけたほうがよいかもしれません。

Angular と IncrementalDOM

Angular は JSX と仮想 DOM という組み合わせではなく、テンプレートと IncrementalDOM 方式といった更新方法を採用しています。

React と決定的に違うのは仮想 DOM に類するツリーを作成するのは初回のみで、2 回目以降は比較して差分を取るということをしていません。ツリーはコンポーネントへの参照ノードが存在し実 DOM ツリーは変更に応じてその場で更新されます。ハンドラの作成、リスナの取り付けなど render で毎回実行されるわけではないというのがポイントです。

前述の @Component メタデータで注入されたテンプレートはコンパイルされると以下のように作成時・更新時のテンプレーティングに振り分けられます。

@Component({
  selector: "app-itemlist",
  template: `
    <div *ngFor="let item of itemList | async">
      {{ item.description }}
    </div>
  `,
})
class ItemListComponent {
  itemList: Observable<Item[]> = this.store.select("item");
  constructor(private store: Store<AppState>) {}
}

/* コンパイルされるコードの一部 */
if (rf & 1) {
  // DOM 作成
  pipe(1, "async");
  template(0, ItemListComponent_div_Template_0, 2, 1, null, _c0);
}
if (rf & 2) {
  // DOM 更新
  elementProperty(0, "ngForOf", bind(pipeBind1(1, 1, ctx.itemList)));
}

React のヒューリスティクスに任せるのか、初回のツリー作成のみでメモリフットプリントを最適化するのか、それぞれアプローチが違うことは知っていても損はしないでしょう。

UI コンポーネントを構成するもの

JSX かテンプレートか再び

React は前述通り関数を作成し JSX を返すのみですが、JSX のほかにも null, number, string, array などを返すこともできます[1]。非常にハンディでシンプルな API は、結果となる DOM を返す・差分検知と更新は React が判断してくれるといった、開発者が UI に集中しやすい体験を提供していると言ってもよいでしょう。

非同期処理をするために関数が Promise を返すことはできないのかという議論は何度もあったと思いますが、throw Promise を Suspense で受け取るといった API を React は提供しています。例外送出の手段であった throw 文で Promise を…? といった感覚になったのは私だけではないはずです。このあたりは Dan Abramov 氏のポストを読むことで少し理解のとっかかりになりそうです。

Angular ではクラス構文とデコレータを利用し、HTML に似たテンプレートもつといった点はここまでで説明したとおりです。JSX が結果の実 DOM を体現している一方で Angular HTML テンプレートの表現力は JavaScript の文脈から分離されています。とはいえ、IncrementalDOM でも触れたように最終的には JavaScript コンテキストにマージされるので知識として取り込んでおくと、実 DOM の結果もイメージしやすいでしょう。

ここで重要なことは DOM の更新タイミングを人間が関知していない点です。値の変更を受けると UI が変更されることに気をとられないということは、集中してアプリケーションの開発に臨めるということです。

スタイル(CSS)の扱いについて

React はスタイルについて関心がなくライブラリの取り扱う範囲に含まれません

style 属性を要素のスタイリングの主要な手段として使うことは一般的に推奨されません。 多くの場合、className を使って外部の CSS スタイルシートに定義された CSS クラスを参照するべきです。

scoped なスタイルとコンポーネントに閉じたスタイルを実現するサードパーティはいくつかありますが、スタイルについての React のスタンスとしては初手まずは CSS ファイルを作成し className を使ってスタイルを適用しましょう、です。Facebook 自身は Stylex という CSS-in-JS のライブラリを内製してドッグフーディングしているはずですが、まあそれはさておき…。

Angular はフルスタックなので CLI で Angular プロジェクトを作成すると CSS をどういった形式で扱うかを尋ねられます。

スキャッフォルドでスタイルの選択が聞かれる

ビルドされランタイムで実行されると、グローバルな CSS 以外は必要なコンポーネントが画面で利用された際に DOM へ生成される、もちろん scoped であるなど必要十分な要素を提供してくれます。

脚注
  1. React.FC, VFC などの型を使っていると誤解しそうなのでご注意ください。該当 Issue ↩︎