画面をどのように更新するかには、大きく命令的 UI と宣言的 UI の二つの考え方があります。React、Vue、Angular、Svelte などのフレームワークを学ぶ前にこの違いをつかむと、状態管理や再レンダーの役割が理解しやすくなります。
フロントエンドの変化と UI の複雑さ
初期の Web は、サーバーが返す HTML 文書を閲覧する用途が中心でした。ブラウザ上で動きのある機能が増えると、JavaScript で DOM を直接操作する方法が広く使われるようになります。
小さな機能なら、要素を探して内容を書き換える方法はわかりやすいものです。一方で、入力フォーム、一覧の絞り込み、通知、通信中の表示などが一つの画面に増えると、どの処理がどの DOM を更新したかを追いにくくなります。UI の見た目そのものが状態の保存場所になるためです。
この複雑さに対して、状態から UI を組み立てる宣言的 UI が広まりました。開発者は「画面をどう変えるか」ではなく「この状態なら何を表示するか」を記述します。
命令的 UI との違い
命令的 UI では、画面を変更する手順を列挙します。たとえば、ログインの成功後に表示を切り替えるなら、「フォームを隠す」「ユーザー名を取り出す」「名前を画面に書く」といった操作を順番に行います。
宣言的 UI では、状態と UI の対応を記述します。ログイン状態に応じた表示なら、考え方は次のようになります。
ログイン済みなら: ユーザー名を表示する
未ログインなら: ログインへの導線を表示する
状態が変わると、フレームワークが必要な差分を画面へ反映します。開発者は以前の表示をどう片付けるかを通常は意識せず、各状態で正しい UI を返すことに集中できます。
| 観点 | 命令的 UI | 宣言的 UI |
|---|---|---|
| 主に記述すること | DOM を変更する手順 | 状態ごとの UI |
| 更新の責任 | 開発者が要素を直接更新する | フレームワークが差分を反映する |
| 複雑になりやすい点 | 操作の順序と更新漏れ | state の設計とデータの流れ |
UI を状態の関数として考える
宣言的 UI では、コンポーネントは props や state などの入力を受け取り、UI を返す関数と考えられます。同じ入力なら同じ UI を返すように保つと、画面の振る舞いを追いやすくなります。
UI = f(現在の状態)
各フレームワークで具体的な書き方は異なりますが、「状態から UI を導く」という中心的な考え方は共通です。宣言的 UI は DOM 操作を完全になくす魔法ではありませんが、画面更新の多くを一貫した形で扱えます。
具体例で比べる
検索条件が空かどうかで「検索」ボタンを有効・無効にする場面を考えます。命令的な実装では、入力イベントごとに値を読み、ボタン要素を取得し、disabled 属性を変更します。初期表示、値の復元、別の処理による入力変更でも同じ更新を忘れずに行う必要があります。
宣言的な実装では、ボタンの状態を検索条件から導きます。
disabled = query.trim() が空かどうか
入力値が変われば UI の計算結果も変わるため、初期表示と更新後で別々の DOM 操作を管理する必要がありません。状態を一か所に定め、そこから UI を導くことが重要です。
宣言的でも設計は必要
宣言的 UI では更新手順の負担は減りますが、state の設計が不要になるわけではありません。次のような問題は依然として起こります。
- 同じ意味の値を複数の state に保存し、表示が食い違う
- どのコンポーネントが state を持つべきか曖昧になる
- 外部 API やブラウザ API との同期をレンダー中に行ってしまう
まず「この画面を変える事実は何か」を state として定義し、他の値はできるだけそこから導出します。DOM を直接操作する必要がある場面は、フォーカス制御や外部ライブラリとの連携など、フレームワークの境界に限定すると見通しを保ちやすくなります。
まとめ
命令的 UI は手順を、宣言的 UI は状態ごとの結果を中心に記述します。画面が複雑になるほど、状態から UI を導くモデルは更新漏れを減らし、変更の影響を追いやすくします。個別フレームワークの API は異なっても、この視点は共通の土台になります。