React の歴史と応用#
応用
- フロントエンドアプリケーション開発、例えば Facebook、Instagram、Netflix のウェブ版。
- モバイルネイティブアプリケーション開発、例えば Instagram、Discord、Oculus。
- Electron と組み合わせてデスクトップアプリケーション開発。
歴史
- 2010 年、Facebook はその php エコシステムに xhp フレームワークを導入し、初めてコンポジションコンポーネントの考え方を取り入れ、後の React の設計に影響を与えました。
- 2011 年、Jordan Walke は FaxJS を創造し、後の React のプロトタイプとなりました:
- クライアントサイドでもサーバーサイドでもレンダリング可能
- レスポンシブで、状態が変わると UI が自動的に更新される。
- 性能が良く、迅速にレンダリング
- 高度にカプセル化されたコンポーネント、関数型宣言
- 2013 年、React は正式にオープンソース化され、2013 年の JSConf で Jordan Walke がこの新しいフレームワーク:React を紹介しました。
- 2014 年から現在にかけて、エコシステムが大爆発し、React に関連する新しいツールやフレームワークが次々と登場しました。
React の設計思想#
UI プログラミングの痛点#
- 状態が更新されると、UI が自動的に更新されず、手動で DOMを呼び出して更新する必要がある。
- 基本的なコードレベルでのカプセル化と隔離が不足しており、コードレベルでのコンポーネント化がない。
- UI 間のデータ依存関係は手動で維持する必要があり、依存チェーンが長い場合、「Callback Hell」(コールバック地獄)に直面することになる。
レスポンシブと変換式#
変換式システム:与えられた入力から出力を求める、例えばコンパイラ、数値計算
レスポンシブシステム:イベントをリッスンし、メッセージ駆動、例えば監視システム、UI インターフェース
イベント -> 既定のコールバックを実行 -> 状態変化 -> UI 更新
レスポンシブプログラミングとコンポーネント化#
したがって、私たちは上記の痛点を解決したいと考えています:
- 状態更新時に UI が自動的に更新される。
- フロントエンドコードのコンポーネント化、再利用可能でカプセル化可能。
- 状態間の相互依存関係は、宣言するだけで済む。
コンポーネント化
- コンポーネントは原子コンポーネントまたはコンポーネントの組み合わせです。
- コンポーネント内部には状態があり、外部からは見えません。
- 親コンポーネントは状態をコンポーネント内部に渡し、子コンポーネントの動作を制御できます。
状態の帰属問題#
現在の価格は Root ノードに属します!下に伝播する必要があるため、これは実際には合理的ではありません。下記の状態管理ライブラリでこの解決方法について説明します。
状態は、二つのノード(または複数)の最近の共通祖先に帰属すべきです。
考察:
- React は単方向データフローですか、それとも双方向データフローですか?
答え:単方向です。常に親コンポーネントが子コンポーネントに何かを渡しますが、これは子コンポーネントが親コンポーネントの状態を変更できないことを意味しません。
- 状態の不合理な上昇問題をどう解決しますか?
答え:状態管理ライブラリを通じて、次に説明します。
- コンポーネントの状態が変更された後、どうやって DOM を更新しますか?
答え:React の実装で説明します。
コンポーネント設計:
- コンポーネントは状態と UI のマッピングを宣言します。
- コンポーネントには Props(外部)/State(内部)の二種類の属性があります。
- Props は親コンポーネントから渡された状態を受け取ります。
- State は内部の属性です。
- 他のコンポーネントに組み込まれることができます。
ps:小プログラムを学んだことがある人は、小プログラムの属性の双方向バインディングが実際にこの考え方を利用していることを知っているでしょう。
ライフサイクル#
マウント -> 状態更新 -> アンマウント
React(hooks)の書き方#
React Hook については公式ドキュメントを参照してください。以下の内容の大部分は:Hook の紹介 – React (reactjs.org)から抜粋しています。
Hook は React 16.8 の新機能です。これにより、クラスを作成せずに状態やその他の React の機能を使用できます。
State Hook#
import React, {useState} from 'react';
function Example() {
// "count"という名前のstate変数を宣言します。
const [count, setCount] = useState(0);
return (
<div>
<p>あなたは{count}回クリックしました</p>
<button onClick={() => setCount(count + 1)}>
クリックしてください
</button>
</div>
);
}
上記の関数はカウンターを表示するためのもので、ボタンをクリックするとカウンターの値が自動的に増加します。
ここで、
useState
は一つのHookです。関数コンポーネント内でこれを呼び出すことで、コンポーネントに内部状態を追加します。React は再レンダリング時にこの状態を保持します。useState
は一対の値を返します:現在の状態とその更新関数であり、イベントハンドラや他の場所でこの関数を呼び出すことができます。
useState
の唯一の引数は初期状態です。上記の例では、カウンターはゼロから始まるため、初期状態は0
です。this.state
とは異なり、ここでの状態は必ずしもオブジェクトである必要はありません —— 必要に応じてオブジェクトにすることもできます。この初期状態の引数は、最初のレンダリング時にのみ使用されます。
Hook は、関数コンポーネント内で **“フックイン”** して React の状態やライフサイクルなどの機能を使用できる関数です。
一つのコンポーネント内で State Hook を複数回使用できます:
function ExampleWithManyStates() {
// 複数のstate変数を宣言します!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Hooksを学ぶ' }]);
// ...
}
Effect Hook#
関数副作用とは、関数を呼び出すときに、返り値以外に主呼び出し関数に他の追加の影響を与えることを指します。例えば、グローバル変数(関数外の変数)を変更したり、引数を変更したりします。純粋関数とは、関数副作用がない関数を指します。これは js のセクションで説明されています。
例えば、以下のコンポーネントは React が DOM を更新した後にページタイトルを設定します:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// componentDidMountとcomponentDidUpdateに相当します:
useEffect(() => {
// ブラウザのAPIを使用してページタイトルを更新します
document.title = `あなたは${count}回クリックしました`;
});
return (
<div>
<p>あなたは{count}回クリックしました</p>
<button onClick={() => setCount(count + 1)}>
クリックしてください
</button>
</div>
);
}
useEffect
を呼び出すと、React にDOM の変更が完了した後にあなたの “副作用” 関数を実行するように指示していることになります。副作用関数はコンポーネント内で宣言されているため、コンポーネントの props や state にアクセスできます。
Hook の使用法則#
Hook は JavaScript の関数ですが、それを使用する際には二つの追加のルールがあります:
- 関数の最外層でのみ Hook を呼び出すことができます。ループ、条件判断、または子関数内で呼び出さないでください。
- React の関数コンポーネント内でのみ Hook を呼び出すことができます。他の JavaScript 関数内で呼び出さないでください。(Hook を呼び出すもう一つの場所は、カスタム Hook 内です)
同時に、これらのルールを自動的に実行するためのlinter プラグインを提供しています。これらのルールは一見すると制限があり、混乱を招くかもしれませんが、Hook が正常に機能するためには非常に重要です。
以上は React 公式ドキュメントの説明です。
React の実装#
三つの問題:
- JSXは JS 標準に準拠していない構文です。
- 返される JSX が変更されたとき、どうやって DOM を更新しますか?
- State/Props が更新されたとき、render 関数を再度トリガーする必要があります。
問題 1#
解決策は非常に簡単で、JSX を JS 構文に変換することです。
const element = (
<h1 className="greeting">
こんにちは、世界!
</h1>
);
// 等価
const element = React.createElement(
'h1',
{className: 'greeting'},
'こんにちは、世界!'
);
問題 2#
返される JSX 自体は DOM に似たものであり、DOM ではありません。DOM 操作自体は非常にパフォーマンスを消費します。したがって、返される JSX と元の DOM 構造の diff(差分)を計算する必要があります。しかし、この diff アルゴリズム自体はあまり時間をかけず、できるだけ小さく、できるだけ短くする必要があります。
Virtual DOM (仮想 DOM)#
Virtual DOMは、実際の DOM と同期するために、JS メモリ内で維持されるオブジェクトであり、DOM に似たツリー構造を持ち、DOM と一対一の関係を築くことができます。
この方法は、React に宣言的な API を与えます:あなたは React に UI がどのような状態であるべきかを伝え、React はその状態に DOM が一致することを保証します。これにより、属性操作、イベント処理、手動 DOM 更新など、アプリケーションを構築する際に必要な操作から解放されます。
状態更新 -> Virtual DOM と実際の DOM の diff 比較 -> Virtual DOM を再レンダリングし、実際の DOM を変更します。
どうやって Diff を計算するのか?#
更新回数を少なく <- トレードオフ -> 計算速度を速く
完璧な最小 Diff アルゴリズムは、O (n^3) の複雑度が必要です。
理論的な最小 Diff を犠牲にして時間を得て、O (n) の複雑度のアルゴリズムを得ました。これは局所的に最適です。Heuristic O (n) Algorithm (ヒューリスティックアルゴリズム)
- 異なるタイプの要素 -> 置き換え
- 同じタイプの DOM 要素 -> 更新
- 同じタイプのコンポーネント要素 -> 再帰
このアルゴリズムは一度の走査で diff を計算できます。
これも React の欠点の一つであり、あるコンポーネントが変更されると、その子コンポーネントはすべて再レンダリングされます。この問題を解決するには、次に説明する React の状態管理ライブラリを見ていく必要があります。
React の状態管理ライブラリ#
核心思想#
状態を UI の外部に抽出して統一管理することですが、これはコンポーネントの再利用性を低下させるため、一般的にはビジネスコードに現れます。以下のフレームワークのいずれかを使用できます。
- Redux 中国語ドキュメント
- XState - JavaScript 状態機械と状態チャート
- MobX 紹介・MobX 中国語ドキュメント
- Recoil 中国語ドキュメント | Recoil 中国語サイト (recoiljs.cn)
状態機械#
外部イベントを受け取った後、現在の状態に基づいて次の状態に遷移します。
どの状態が状態管理ライブラリに適しているか?
- 多くの階層のコンポーネントで使用される可能性のある状態
アプリケーションレベルのフレームワークの普及#
React 自体は、ルーティング、ページ設定などの十分なエンジニアリング能力を提供していません。
- Next.js - React アプリ開発フレームワーク シリコンバレーのスタートアップ企業 Vercel の React 開発フレームワークで、安定しており、開発体験が良く、Unbundled Dev、sWC などをサポートし、同様に Serverless のワンクリックデプロイプラットフォームが開発者の迅速なデプロイを支援します。スローガンは「Web をより速くしよう」です。
- Modern.js - 現代 Web エンジニアリングシステム (modernjs.dev) バイトダンス Web Infra チームが開発したフルスタック開発フレームワークで、多くの即使用可能な機能とベストプラクティスが含まれており、ツールの調査選択にかかる時間を大幅に削減できます。
- Get Started with Blitz (blitzjs.com) API 不要のフルスタック開発フレームワークで、開発中に API 呼び出しや CRUD ロジックを書く必要がなく、フロントエンドとバックエンドが密接に連携する小規模チームプロジェクトに適しています。
課題#
- React コンポーネントの render 関数は、どのようなタイミングで再実行されますか?
- React のこの関数型プログラミングと、Vue のこのテンプレート構文に基づくフロントエンドフレームワークは、それぞれどのような利点と欠点がありますか?
- React はコンポーネントの再利用に組み合わせを使用することを推奨していますが、継承ではなく、その背後にはどのような考慮がありますか?
個人的理解
まとめと感想#
本講義では、React およびその原理、コンポーネント化、いくつかのアプリケーションレベルのフレームワークについて概説しました。
本文で引用した内容の大部分は、牛岱先生の講義および React 公式ドキュメントからのものです。