UI実装のステップまとめ
参考資料
UI実装時の5ステップ
- UIをコンポーネントの階層に分割する
- 単一責任の原則を利用するのも良い。
- 単一責任の原則は、モジュール・クラス・関数は単一の機能について責任を持ち、その機能をカプセル化すべきというもの。
- つまり、1つのコンポーネントは理想的には1つのことだけを行うべきで、もし大きくなったらより小さなサブコンポーネントに分解すべき、という考え。
- JSONがうまく構造化されている場合、それがUIのコンポーネント構造に自然に対応していることがよくあるらしい。
- 単一責任の原則を利用するのも良い。
- Reactで静的なバージョンを作成する
- インタラクティブな要素はまだ加えない。(JSXを返す以外のことはしないコンポーネント)
- データモデルからUIを描画するバージョンを作成する。
- 単純なものならトップダウンが簡単。
- 大規模なものならボトムアップが良い。
- UIの状態を最小限かつ完全に表現する方法を見つける
- アプリケーションが必要とする状態に関する必要最小限の表現を見つけ出し、他の全てのものは必要になったらその場で計算する。
- stateを保持すべき場所を特定する
- 次の手順に従って解決できる。
- そのstateに基づいて何かをレンダーする全てのコンポーネントを特定する。
- それらの最も近い共通の親コンポーネントを見つける。
- stateをどこにあるべきかを決定する。
- 多くの場合、stateをその共通の親に置くことができる。
- その共通の親の更に上のコンポーネントに置くこともできる。
- stateを保持するのに適切なコンポーネントが見つからない場合、stateを保持するためだけのコンポーネントを作成し、共通の親コンポーネントの階層の上のどこかに追加する。
- 次の手順に従って解決できる。
- 逆方向のデータフローを追加する
- propsとstateが階層構造の下方向に向かって表示されるが、ユーザの入力等を受け取りstateを変更する場合は、逆方向のデータの流れをサポートする必要がある。
stateとは
- アプリが記憶する必要がある、変化するデータの最小限のセット。
- 時間が経てば変わるもの。
- 親からprops経由で渡されないもの。
- 既存のstateやpropsに基づいて計算可能でないもの。
- stateの構造を考える上で最も重要な原則がDRY(Dont’t Repeat Yourself)。
- コンポーネントにstateを追加するにはuseStateフックを使用する。
- 同じコンポーネントが同じ場所でレンダーされる時に、stateを保持する。
- 同じコンポーネントが同じ場所でレンダーされる時に、keyを渡すことでstateを共有すべきでない異なるコンポーネントとしてReactに扱わせることができる。
コンポーネントがレンダーされる2つの要因
- コンポーネントの初回レンダー
- コンポーネント(またはその祖先のいずれか)のstateの更新
propsとは
- 親から子へデータを渡すための手段。
- 常にpropsの値が固定とは限らない。
- コンポーネントのpropsが変わらないといけない場合、親のコンポーネントに別のprops、新しいオブジェクトを渡してもらう必要がある。
- レンダー毎に新しいバージョンのpropsを受け取る。
- propsは子から書き換え不可?
コンポーネントの設計・実装
参考資料
React公式のドキュメント:state を使って入力に反応する
宣言型UIと命令型UI
結論:宣言型UIの考え方で組むためにReactは作られた。
命令型UIとは設計・実装が、次のような考えに基づいたもの。
- インタラクティブなUIを設計する際、ユーザのアクションに応じてUIがどのように変化するかを考えることが多い。
- 起こったことに応じてUIを操作するための命令そのものを書かなければならない。
宣言型UIとは設計・実装が、次のような考えに基づいたもの。
- 表示したいものを宣言する。(視覚状態ごとにUIを記述する)
宣言型UI実装時の5ステップ
- コンポーネントの様々な視覚状態を特定する
- 例:有効化・無効化・ローディングを表すスピナが表示・エラーメッセージ表示等。
- ステートマシンの考え方(状態遷移図)が役立つ。
- 多くの視覚状態をリスト形式で表示するとわかりやすくなるかもしれない。
- それらの状態変更を引き起こすトリガを決定する
- 例:クリックされる・入力される・サーバからのレスポンス等。
- useStateを使用してメモリ上にstateを表現する
- 考えられる全ての視覚状態を確実にカバーできる十分な数のstateを追加する。
- stateのリファクタリングはプロセスの一部なので、この時点で完全でなくても良い。
- 必要不可欠でないstate変数を全て削除する(バグや矛盾を避けるため)
- 目標は、メモリ上のstateがユーザに見せたい有効なUIを表現しないという状況を防ぐこと。
- 次の確認をすると良い。(useReducerの利用も検討すると良い)
- stateで矛盾は生じないか?例:stateAとstateBの両方がtrueとなることはありえないなら、それらのstateをまとめることができるかもしれない。
- 同じ情報が別のstateから入手できないか?例:リストの個数をstateに保存せずに、lengthプロパティで取得する等。
- 別のstate変数の逆をとって同じ情報を得られないか?
- イベントハンドラを接続してstateを設定する例:ボタンがクリックされたら特定のstateが更新される等。
state構造
より良い選択をするため、次の5つの原則がある。
これらの原則はミスを入り込ませずに容易にstateを更新できるようにすることを目標としている。
- 関連するstateをグループ化する2つ以上の変数を常に同時に更新する場合、それらを単一のstateにまとめることを検討する。
- stateの矛盾を避ける例:stateAとstateBの両方がtrueとなることはありえないなら、それらのstateをまとめることができるかもしれない。
- 冗長なstateを避けるpropsや他のstateから計算できる場合、そのstateは不要。
- state内の重複を避ける同一データが複数のstateや他のオブジェクトで重複している場合、それらを同期させるのは困難なので、できる限り重複を減らす。
- 深くネストされたstateを避ける深い階層構造となっているstateは更新しやすくない。できる限りフラットに構造化する方法を選ぶと良い。
追加で、基本的にpropsをstateにコピーしない、ということも大事で、親コンポーネントから渡されたpropsと同期されない状態を防ぐことができる。
新しい値が来ても無視されるということを明示したい場合、propsの名前をinitialやdefaultで始めるとわかりやすくて良い。
カスタムフック
参考資料
React公式のドキュメント:カスタムフックでロジックを再利用する
要点
- フックの名前は常にuseで始まるローワーキャメルケース。
- フックを呼び出さない関数の名前は、useで始めない。
- stateを共有するためではなく、stateのロジックを共有するもの。
- カスタムフック内のコードは、コンポーネントの再レンダーごとに実行される。
- useEffectを利用する場合、カスタムフックにラップするとわかりやすくならないか検討すると良い。
- カスタムフックが受け取るイベントハンドラはuseEffectにラップする。
- カスタムフックの名前は、何をするのか、何を受け取るのか、何を返すのかを推測できるほどに明確であるべき。
- 良いカスタムフックは、動作を制約することで呼び出す側のコードをより宣言的にするもの。
イベントハンドラとuseEffectの使い分け
参考資料
React公式のドキュメント:エフェクトからイベントを分離する
特徴
- イベントハンドラ
- ユーザが同じ操作を繰り返した場合にのみ再実行される。
- 具体的なユーザ操作に反応して実行される。
- 手動でトリガされるもの。
- イベントハンドラ内のロジックはリアクティブでない。
- エフェクト
- propsやstateのような、読み取る値が前回のレンダー時と異なる場合に再同期を行い、その結果再描画される。
- エフェクト内のロジックはリアクティブである。
- 自動でトリガされるもの。
エフェクトについて(useEffect)
参考資料
一般的にエフェクトが不要な場合
- レンダーのためのデータ変換。
- ユーザイベントの処理。