React Docs BETAを読む 【Stateで入力に反応する】

2022/07/07に公開

というわけ昨日に引き続いてドキュメント読んでいきます。
今日は、React Docs Beta:Reacting to Input with Stateを読んでいきます。

Reacting to Input with State

直接UIを操作するのではなく、取りうる状態を記述して状態によってそれを切り替える、というのがReactのコンポーネントの書き方です。
Managing Stateで書かれていた節を掘り下げた章となっています。

How declarative UI compares to imperative

命令形と宣言型の違いについての節ですね。
例えばあるフォームを実装するときに以下のような挙動が考えられます。

  • フォームに入力するとボタンが活性化する。
  • ボタンが押下されて、通信している間は非活性になる。
  • 通信が終了すると、Thank youというメッセージが表示される。
  • 通信に失敗するとエラーを表示して、フォームを最有効にする。

命令型ではこの挙動が実装にそのまま対応します。
UIを操作する命令をひとつひとつ正確に書く必要があるということですね。
行き先を知らない運転手に横で道案内をすることを考えてみるとよいと考えてありますね。

命令型ではシステムが複雑になればなるほど指数関数的に管理が難しくなります。
例えば上記フォームで1つであればそこまでではありませんが、数が多くなってくると手間がどんどん増えますね。

一方、Reactの場合はUIを直接操作しません。
表示したいものを宣言するとReactがUIを更新してくれます。
道案内の例だと、タクシーにのって行き先を伝えれば運転手がそこに連れて行ってくれるイメージですね。

Thinking about UI declaratively

ここで上記で考えたフォームの実装をReactで再実装する手順を考える節です.
以下の手順でUIを再実装していきます。

  1. コンポーネントの視覚状態を特定する。
  2. 状態変化のトリガーを特定する。
  3. useStateでメモリに状態を表現する。
  4. 必須でない状態変数を削除する。
  5. イベントハンドラで状態を更新する。

1. Identify your component’s different visual states

視覚状態を特定していくステップです。
デザイナーが様々な見た目のモックアップするように表示される可能性のあるUIの状態を視覚化していきます。
例に出ているフォームであれば

  • 空:送信ボタンが非活性
  • 入力:送信ボタンは活性
  • 送信中:フォームが無効になりスピナーが表示される
  • 送信成功:フォームの代わりにありがとうのメッセージが表示
  • エラー:入力状態と同じで、エラーメッセージが表示される。

ここである状態によって制御できるモックを作成していきます。
状態はハードコーディングで、直接変更して見た目が変わることが確認できれば良いようです。

2. Determine what triggers those state changes

状態変化のトリガーを特定するというステップですね。
以下の2つのトリガーで状態を更新することができます。

  • ボタンクリックなどの人間からの入力
  • ネットワークのレスポンスやタイムアウトなどのコンピュータからの入力

どちらの場合でも状態変数に設定する必要があります。
フォームの場合は、テキストエリアの入力やネットワーク応答で各ボタンやフォームの制御を行うという感じですね。
このときに状態遷移図を書くといいようです。
ここで状態の遷移とトリガーを整理するイメージですね。

3. Represent the state in memory with useState

今度は先程定義した状態を表現するというステップですね。
先程定義した状態をuseStateを使ってメモリ上で表現します。
重要なのは、より単純にするということです。状態が少なければ少ないほどバグは少なくなります。
まずは絶対に必要なものから定義するということですね。
フォームの例だと、入力する回答とエラーの状態は必ず必要です。

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);

次に先程定義した状態を網羅するように状態変数を定義します。

const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);

この段階では、特に最適解である必要はなくリファクタリングすれば問題ありません。

4. Remove any non-essential state variables

必要でない状態変数を削除するステップです。
Managing Stateでも書かれていた通り、状態変数の重複や冗長さはバグを生み出します。
ステップ3の状態から重複を削除するようにリファクタリングすることでよりわかりやすく、シンプルなコンポーネントにすることができます。

  • パラドックスは起きていないか。
  • 同時に存在しない状態の組み合わせはないか。
  • 別の状態変数から同じ情報がとれないか。

などを考えていきましょう。
フォームの例では上にあった状態変数が7つから3つに減っています。

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'

5. Connect the event handlers to set state

最後はイベントハンドラで状態を更新してあげるステップです。
状態遷移図で定義した各トリガーに対して状態を設定するように実装します。
最終的に上記手順で作成したものは命令型のそれよりも、拡張性、保守性に優れていて直感的です。

まとめ

こういう風にして手順化すると非常にわかりやすいですね。
なんだかんだ順番に考えずに行き当たりばったりで状態を定義することがありましたが、
冗長さや重複を防ぐためにも、コンポーネントの見せたい状態をしっかりと把握することが大事なのだと思いました。

GitHubで編集を提案

Discussion