blog

JSコア理論 Reactの基本概念と仮想DOM

文字列でもHTMLでもないJSXは、本質的にはReact要素と呼ばれる()を介して作成されるオブジェクトの構文拡張であり、それに近いものです。 ReactはJSXの使用を強制するのではなく、マークアッ...

Jan 7, 2021 · 13 min. read
シェア

React

JSX

const element = <h1>Hello, world!</h1>;

React.createElement()文字列でもHTMLでもないJSXは、基本的にJavaScriptの構文を拡張したもので、React要素と呼ばれるオブジェクトを経由して作成されるJavaScriptに近いものです。

要素レンダリング

  1. React要素は不変のオブジェクトです。一度作成したら、子要素や属性を変更することはできません。UIを更新する唯一の方法は、まったく新しい要素を作成してReactDOM.render()に渡すことです。

  2. Reactは更新が必要なものだけを更新します。React DOMは、要素とその子の状態を比較し、DOMを望ましい状態にするために必要な更新のみを行います。

コンポーネントと小道具

  • ファンクションコンポーネント:データを持つユニークな「props」オブジェクトを受け取り、React要素を返します。このタイプのコンポーネントは、本質的にJavaScript関数であるため、「関数コンポーネント」と呼ばれます。
  • クラスコンポーネント:以下のような形をしたコンポーネントです。
function Welcome(props) {
 return <h1>Hello, {props.name}</h1>;
}
class Welcome extends React.Component {
 render() {
 return <h1>Hello, {this.props.name}</h1>;
 }
}
  • 読み取り専用のプロップ: すべてのReactコンポーネントは、純粋な関数であるかのように、プロップが変更されないように保護する必要があります。
  • ステートによって、Reactコンポーネントはユーザーのアクションやネットワークの応答、その他の変化に応じて動的に出力を変更することができます。

このような関数は、入力パラメータを変更しようとせず、同じ入力パラメータを複数回呼び出しても常に同じ結果を返すため、「純粋関数」と呼ばれます。

状態とライフサイクル

setState(updater,[callback])

Reactでは、setStateを呼び出しても、それがReactによって発生したイベントハンドラであれば、this.stateは同期的に更新されません。

なぜ非同期なのですか?もしsetStateが同期的に状態を更新し、状態の更新がコンポーネントの再レンダリングをトリガーするのであれば、setStateするたびにコンポーネントをレンダリングすることになり、パフォーマンスを大きく損ないます。

  1. 通常のReactバウンドイベント:非同期更新
  2. addEventListener 経由でバインドされるイベント: 同期アップデート
  3. setTimeouttによるクリックイベントの処理:同期更新

compoentDidUpdateまたはsetStateコールバック関数を使用して、アプリケーションの更新後にこれらの関数がトリガされるようにします。バッチ更新は、キューと変数ロックisBatchingUpdatesに基づいて実装されています。

ステートの姿勢を正しく使いましょう:

  1. Stateを直接変更しないでください。

  2. setStateを呼び出しても、すぐに更新されるわけではありません。

  3. すべてのコンポーネントが同じ更新メカニズムを使用し、すべてのコンポーネントがdidmountすると、親コンポーネントがdidmountし、更新を実行します。

  4. 更新は各コンポーネントからの更新とマージされ、各コンポーネントは更新ライフサイクルを一度だけトリガーします。

  5. フック関数と合成イベントで

reactまだ彼の更新メカニズムでは、今度はisBranchUpdateをtrueにする。リアクトのライフサイクルや合成イベントでは、.

上記の処理に従うと、この時点ではsetStateが何度呼ばれても更新は実行されず、代わりに更新される状態が_pendingStateQueueに、更新されるコンポーネントがdirtyComponentに格納されます。

ライフサイクルの場合、最後の更新メカニズムが実行されると、すべてのコンポーネント、つまり最上位のコンポーネントdidmountがisBranchUpdateをfalseに設定します。この時点で、累積のsetStateが実行されます。

  1. 非同期関数とネイティブイベント

実行メカニズム上、setState自体は非同期ではありませんが、setStateの呼び出しの中で、reactが更新中であれば、現在の更新はステージングされ、実行中の最後の更新の実行を待つことになり、この処理は一種の非同期的な錯覚を与えます。

ライフサイクルでは、JSの非同期メカニズムに従って、非同期関数が最初にステージングされ、すべての同期コードが実行された後、最後の更新処理が実行されたときに実行されます。

isBranchUpdateがfalseに設定され、上記の処理によると、呼び出しsetStateはすぐに更新を実行することができ、更新結果を取得します。

  1. componentDidMountはsetstateを呼び出します。

これは追加のレンダリングをトリガしますが、ブラウザが画面をリフレッシュするときに発生します。これにより、render()が2回呼び出されたとしても、ユーザーに中間状態が表示されることはありません。

componentDidMountそれ自身は更新中であるsetStateを再度呼び出すと、将来的に別のレンダリングを行うことになり、不必要なパフォーマンスの無駄が生じますが、ほとんどの場合は初期値を設定することで対処できます。

  1. 推奨:setStateを呼び出すときにステート値を渡す関数を使用し、コールバック関数で最新の更新されたステートを取得します。

ライフサイクル。

  1. マウント コンポーネントのインスタンスが作成され、DOMに挿入されると、そのライフサイクルは以下の順序で起動されます:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()

注:以下のライフサイクル・メソッドは時代遅れになりつつあるので、新しいコードでは避けるべきです:UNSAFE_componentWillMount()

  1. 更新 更新は、コンポーネントのプロップまたは状態が変更されたときにトリガされます。コンポーネントの更新ライフサイクルは以下の順序で起動されます:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()

注意:以下のメソッドは時代遅れになりつつあるので、新しいコードでは避けるべきです:

UNSAFE_componentWillUpdate() UNSAFE_componentWillReceiveProps()

  1. Uninstall 以下のメソッドは、コンポーネントが DOM から削除されるときに呼び出されます:
componentWillUnmount()

イベント処理

  1. Reactでは、falseを返すことでデフォルトの動作を防ぐことはできません。明示的に preventDefault を呼び出す必要があります。

Reactは、独自のイベントメカニズムを実装し、イベントのバブリングとキャプチャのプロセス自体をシミュレートし、イベントプロキシ、一括更新などを使用し、ブラウザ間の互換性の問題をスムーズにします。

  1. Reactイベントとネイティブ・イベントの実行順序
  • reactのすべてのイベントは、ドキュメント
  • reactイベントは、実際のdomがトリガーされたときにのみ処理され、ドキュメントにバブルアップされます。
  • そのため、ネイティブイベントが最初に実行されます
  • 次にreactの複合イベントを実行します。
  • 最後に、実際にドキュメントにマウントされているイベントを実行します。
  1. リアクトイベントとネイティブイベントは混在できますか?

リアクト・イベントとネイティブ・イベントは混在させない方が良いでしょう。

stopPropagationメソッドを実行するネイティブイベントは、他のreactイベントを失敗させます。すべての要素イベントはドキュメントにバブルすることはできない。なぜなら.

thisバインディング:JSXコールバック関数での thisには注意が必要です。JavaScriptでは、デフォルトではクラスのメソッドは thisにバインドされません。

方法は3つあります:

  1. 関数よりもコンストラクタでバインドします:this.handleClick = this.handleClick.bind(this);
  2. クラスがメソッドによってイベント・ハンドラを定義している場合は、矢印関数を使用します: handleClick = () => {console.log('this is:', this);}
  3. コールバック関数内で矢印関数を直接使用します: <button onClick={() => this.handleClick()}>Click me</button>

注目してください:

[パフォーマンス最適化ポイント] Buttonがレンダリングされるたびに、異なるコールバック関数が作成されます。コールバック関数がpropsとしてサブコンポーネントに渡される場合、これらのコンポーネントは追加の再レンダリングを受ける可能性がある。ほとんどの場合、これは問題にはなりませんが、.NET Frameworkのコールバック関数がレンダリングされるたびに異なるコールバック関数が生成されます。このようなパフォーマンスの問題を避けるために、コンストラクタでバインドするか、クラスフィールド構文を使用することをお勧めします。

組み合わせ vs 継承

Reactには非常に強力なコンポジション・パターンがあります。コンポーネント間でコードを再利用するためには、継承よりも組み合わせを使用することをお勧めします。プロップとコンポジションは、明確で安全な方法でコンポーネントの外観と動作をカスタマイズする柔軟な方法を提供します。注意: コンポーネントは、基本的なデータ型、React要素、関数など、あらゆるpropsを受け入れることができます。

  1. レンダリング結果に子コンポーネントを渡すには、特別な{props.children}を使用します。
  2. いくつかのケースでは、コンポーネントにいくつかの「穴」を残す必要があるかもしれません。この場合、childrenを使用する代わりに、独自の規約を作ることができます: 必要なコンテンツをpropsに渡して、対応するpropsを使用します。

Context

このような値を、コンポーネント・ツリーを通してレイヤーごとに明示的に渡すことなく、コンポーネント間で共有する方法。 コンテキストは、小道具でそれを見つける方法を提供します。

コンテキストは、現在認証されているユーザー、テーマ、優先言語など、コンポーネントツリーに対して「グローバル」なデータを共有するように設計されています。

[コードの最適化ポイント] コンテキストの主な適用場面は、異なるレベルの多くのコンポーネントが同じデータにアクセスする必要がある場合です。コンポーネントの再利用性を低下させる可能性があるため、使用には注意が必要です。あるレベルから別のレベルへのプロパティの受け渡しを避けたいだけであれば、コンテキストよりもコンポーネント合成の方が良い解決策になることがあります。

サブコンポーネント自身が渡されるため、中間コンポーネントはサブコンポーネントが使用するプロップを知る必要がない。文脈自由解は.

エラー境界

UIの一部でJavaScriptエラーが発生しても、アプリ全体がクラッシュするべきではありません。これに対処するため、React 16ではエラー境界という新しい概念を導入しています。

<ErrorBoundary>
 <MyWidget />
</ErrorBoundary>

エラー境界は、サブコンポーネントのツリーのどこかで発生したJavaScriptエラーをキャッチして表示し、クラッシュしたサブコンポーネントのツリーをレンダリングする代わりに、代替UIをレンダリングするReactコンポーネントです。

エラー境界は、レンダリング中、ライフサイクルメソッド中、コンポーネントツリー全体のコンストラクタ中のエラーを検出します。

[コード最適化ポイント] エラー境界は、以下のシナリオで発生したエラーを捕捉しません:

  • イベント処理
  • 非同期コード
  • サーバー側レンダリング
  • 自分自身をスローするエラー

Refs転送

Refフォワーディングとは、コンポーネントを経由して子コンポーネントの1つに自動的にRefを渡すテクニックです。このテクニックは、特に高レベルのコンポーネントに有効です。Ref forwarding はオプションの機能で、特定のコンポーネントが ref を受け取り、それを子コンポーネントに渡すことができます。

Fragments

Reactでよくあるパターンは、コンポーネントが複数の要素を返すことです。フラグメントを使用すると、DOMにノードを追加することなく、サブリストをグループ化できます。

render() {
 return (
 <React.Fragment>
 <ChildA />
 <ChildB />
 <ChildC />
 </React.Fragment>
 );
}

または、次の短い構文を使用します。

高次コンポーネント

定義:高次コンポーネントは、引数がコンポーネントで戻り値が新しいコンポーネントである関数です。HOCは入力コンポーネントを変更しませんし、その振る舞いを複製するために継承を使用しません。代わりに、HOCはコンポーネントをコンテナコンポーネントで包むことによって新しいコンポーネントを構成します。

<> </>高次コンポーネントは、Reactでコンポーネントロジックを再利用するための高レベルのトリックです。 HOC自体はReact APIの一部ではありません。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

コンポーネントはプロップをUIに変換し、高次のコンポーネントはコンポーネントを別のコンポーネントに変換します。例えば、Reduxのコネクト

注目してください:

  1. render 関数を呼び出すたびに新しい EnhancedComponent が作成されるため、サブツリーはアンロードされ、レンダリングされるたびに再マウントされます!
  2. 静的メソッドは必ずコピーしてください。hoist-non-react-staticsを使えば、React以外の静的メソッドを自動的にコピーできます。
  3. 審判はパスしません。

サードパーティリポジトリとの相乗効果

はルートDOM要素への参照を追加します。 componentDidMountでは、その参照を取得できるので、jQueryプラグインに渡すことができます。

このルートDOM要素にrefを追加するマウント後にReactがこのDOMに触れるのを防ぐため、DOMは.NET Frameworkからダウンロードされます。この

要素には属性も子もないため、Reactがそれを更新する理由はなく、jQueryプラグインはDOMのこの部分を自由に管理できます。
class SomePlugin extends React.Component {
 componentDidMount() {
 this.$el = $(this.el);
 this.$el.somePlugin();
 }
 componentWillUnmount() {
 this.$el.somePlugin('destroy');
 }
 render() {
 return <div ref={el => this.el = el} />;
 }
}

パフォーマンスの最適化

  1. デプロイ時に製品版を使用し、いくつかの警告メッセージを削除しました。
  2. 長いリストの仮想化。仮想スクロール」テクニック。このテクニックは、限られた時間内に限られた量のコンテンツのみをレンダリングし、コンポーネントの再レンダリングやDOMノードの作成に費やす時間を奇跡的に削減します。react-windowとreact-virtualizedは、人気のある仮想スクロールライブラリです。
  3. 調停は避けてください。ライフサイクル・メソッドshouldComponentUpdateをオーバーライドして高速化するこれは.このメソッドは再レンダリングの前に起動されます。デフォルトの実装は常にtrueを返します。 コンポーネントを更新する必要がないタイミングがわかっている場合は、shouldComponentUpdateでfalseを返すことで、レンダリング処理全体をスキップできます。
shouldComponentUpdate(nextProps, nextState) {
 return true;
}
  1. 不変データの威力。元のオブジェクトを変更しないでください。拡張演算子または Object.assign を使用して新しいオブジェクトを返します。

Diffアルゴリズム

  1. 2つのツリーを比較する場合、Reactはまず2つのツリーのルートノードを比較します。
  • ルートノードの要素の種類が異なる場合、Reactは元のツリーをマウント解除し、新しいツリーを構築します。componentWillUnmount() componentWillMount() -> -> componentDidMount()

  • 同じ型の 2 つの React 要素を比較する場合、React は DOM ノードを保持し、変更されたプロパティのみを比較して更新します。そして、子ノードは再帰的です。

  1. サブノード再帰

子要素のリストの最後に新しい要素を追加する場合、更新のオーバーヘッドは少なくなります。新しい要素をテーブルヘッダに挿入するだけでは、更新のオーバーヘッドが多くなり、後のものを保持することに気づかず、代わりに各子要素を再構築します。この状況はパフォーマンスの問題を引き起こします。これを解決するには、キーを追加します。

同じノードタイプと安定した予測可能なキーを使用するようにしてください。

レンダリングプロップ

レンダリングプロップは、何がレンダリングされる必要があるかをコンポーネントに伝えるために使用される関数プロップです。

レンダリングプロップがレンダリングプロップと呼ばれるのはスキーマのためであり、そのスキーマを使うためにrenderという名前のプロップを使う必要はないことを覚えておくことが重要です。

React.PureComponent.PureComponentでRender Propsを使用する場合は注意が必要です。 render prop Reactの使用を相殺する.PureComponent 利点renderメソッドで関数を作成する場合は、.PureComponentを使用してください。propsの浅い比較は常にfalseになり、この場合、レンダリングごとにrender propの新しい値が生成されるからです。

Keyの使い方は

reactは、原則として、キーに基づいてコンポーネントを破棄して再作成するか、更新するかを決定します:

  • キーが同じでコンポーネントが変更された場合、reactは変更されたコンポーネントのプロパティのみを更新します。
  • キーが異なる場合、reactはコンポーネントを破棄し、コンポーネント全体を再レンダリングします。

インデックスをキーにした場合の問題点

要素のデータソースの順序が変わると、再レンダリングされます。また、一意なIDをキーにした場合、サブコンポーネントの値もキーも変わらず、順番だけが変わるので、reactはそれらを移動させるだけで、再レンダリングはしません。

仮想 DOM

パフォーマンスを改善できるかについて

多くの記事で、VitrualDomはパフォーマンスを向上させることができると書かれていますが、これは実際にはかなり一方的な主張です。

DOMを直接操作することは、非常にパフォーマンスを消費することは間違いありません。しかし、ReactはVitrualDomを使うことでDOMの操作を避けることはできません。

最初のレンダリングでは、VitrualDomには何の利点もない。の場合、より多くの計算を実行し、より多くのメモリを消費しなければなりません。

VitrualDomの利点は、ReactのDiffアルゴリズムとバッチ戦略であり、ページの更新でReactは、事前に、DOMを更新してレンダリングする方法を計算します。 実際には、DOMの直接操作で、この計算プロセスは、それを判断し、独自の実装も可能ですが、それは確かに非常に多くのエネルギーと時間を消費し、多くの場合、自分でそれを行うことはReactのようにありませんの良さではありません。だから、Reactは、このプロセスで "パフォーマンスを向上させる "のに役立ちます。

つまり、VitrualDomはDOM操作よりも高速というよりも、何度もレンダリングする際に、より効率的に更新する方法を考えることで、開発の効率化に役立っているといった方がしっくりきます。

クロスブラウザ互換性 Reactは、VitrualDomに基づいて独自のイベントセットを実装し、イベントのバブリングとキャッチプロセス自体をシミュレートし、イベントプロキシ、バッチ更新などを使用して、ブラウザ間のイベントの互換性の問題をスムーズに解決します。

イベント処理

  • Reactコンポーネントのレンダリング処理
  1. React.createElement(...)ReactコンポーネントはReact.createElementまたはJSXを使用して記述され、事実上すべてのJSXコードは最終的に変換されます。

  2. createElement関数は、keyやrefなどの特別なpropsを処理し、defaultPropsを取得してデフォルトのpropsに値を割り当て、入力される子ノードを処理して最終的にReactElementオブジェクトを構築します。

  3. ReactDOM.renderは、生成された仮想DOMを指定されたコンテナにレンダリングします。このコンテナは、バッチ処理、トランザクション、その他のメカニズム、および特定のブラウザ用のパフォーマンス最適化を採用し、最終的に実際のDOMに変換します。

  • 仮想 DOM の構成
  • XSS対策:Symbol.for('react.element')の助けを借ります。
  • バッチとトランザクションのメカニズム: setState
  • パフォーマンスの最適化: IE/Edge Fragment
  • イベントメカニズム: 仮想 DOM にバインドされたすべてのイベントを実際の DOM イベントにマッピングし、すべてのイベントをドキュメントにプロキシします。
Read next

shiroフレームワークの使用法

Apache Shiro は強力で使いやすい Java セキュリティフレームワークで、認証、認可、暗号化、セッション管理を行います。Shiroの理解しやすいAPIを使用すると、迅速かつ簡単に、最小のモバイルアプリケーションから最大のWebアプリケーションやエンタープライズアプリケーションに至るまで、任意のアプリケーションを取得することができます 最も直接的な、権限の操作に関連するユーザーに直接の一つ - 使用されるコントロールのアクセス許可でshiro ...

Jan 7, 2021 · 6 min read