Part 1 · React の世界観
宣言的 UI
「UI は状態の写像である」の意味
React の説明でよく出てくる「宣言的 UI」という言葉。 日本語として硬いので身構えてしまいますが、 言っていることはひとつだけです。
画面をどう作り変えるかではなく、画面がどうあるべきかを書く。
この違いが分かると、React のコードが「なぜあの形をしているのか」に納得がいきます。
手順を書くやり方(命令的)
ログイン状態によって、画面の 3 か所を変えたいとします。 ヘッダーの名前、本文、ボタンの文字です。
手順として書くと、こうなります。
function login() {
header.textContent = "さとうさん";
body.textContent = "会員向けの内容が表示されています";
button.textContent = "ログアウトする";
}
function logout() {
header.textContent = "ゲスト";
body.textContent = "ログインすると内容が表示されます";
button.textContent = "ログインする";
}間違ってはいません。ただ、同じことを 2 回、逆向きに書いているのが気になります。
そして表示する場所が 1 つ増えるたびに、login と logout の両方に書き足す必要があります。 片方だけ直してしまうと、 「ログアウトしたのにヘッダーだけ名前が残っている」という状態が生まれます。
login-view.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function LoginView() {
// 画面が持っている状態は、この 1 つだけ
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<span className="text-muted-foreground">ヘッダー</span>
<span className="font-semibold">
{isLoggedIn ? "さとうさん" : "ゲスト"}
</span>
</div>
<div className="rounded-md border px-3 py-2">
{isLoggedIn
? "会員向けの内容が表示されています"
: "ログインすると内容が表示されます"}
</div>
<Button size="sm" onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? "ログアウトする" : "ログインする"}
</Button>
</div>
);
}あるべき姿を書くやり方(宣言的)
React では、手順を書きません。書くのはこれだけです。
<span>{isLoggedIn ? "さとうさん" : "ゲスト"}</span>「ログイン中なら名前、そうでなければゲスト」。 これは手順ではなく、事実の記述です。 いつ書き換えるか、という話が一切出てきません。
ボタンを押すと 3 か所が同時に変わる
このデモが持っている状態は isLoggedIn ひとつだけです。true と false を切り替えているだけで、 3 か所を書き換える処理はどこにもありません。
表示する場所が 4 つに増えても、書き足すのはその 1 か所だけです。 「両方に書き足す」必要がないので、片方だけ直し忘れる事故が起きません。
login-view.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function LoginView() {
// 画面が持っている状態は、この 1 つだけ
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<span className="text-muted-foreground">ヘッダー</span>
<span className="font-semibold">
{isLoggedIn ? "さとうさん" : "ゲスト"}
</span>
</div>
<div className="rounded-md border px-3 py-2">
{isLoggedIn
? "会員向けの内容が表示されています"
: "ログインすると内容が表示されます"}
</div>
<Button size="sm" onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? "ログアウトする" : "ログインする"}
</Button>
</div>
);
}画面は、状態から決まる
この考え方は、よくこう表現されます。
UI = f(state)画面は、状態を渡すと結果が返ってくる関数のようなものだ、という意味です。 同じ状態なら、いつ何度描いても同じ画面になります。
だから React では、画面を直接いじりません。状態を変えれば、画面は勝手についてくるからです。
login-view.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function LoginView() {
// 画面が持っている状態は、この 1 つだけ
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<span className="text-muted-foreground">ヘッダー</span>
<span className="font-semibold">
{isLoggedIn ? "さとうさん" : "ゲスト"}
</span>
</div>
<div className="rounded-md border px-3 py-2">
{isLoggedIn
? "会員向けの内容が表示されています"
: "ログインすると内容が表示されます"}
</div>
<Button size="sm" onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? "ログアウトする" : "ログインする"}
</Button>
</div>
);
}理解できたか確かめる
確認クイズ
宣言的 UI とは、どういう書き方のこと?
確認クイズ
React で「ボタンの文字を変えたい」と思ったとき、まず何を考える?
login-view.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function LoginView() {
// 画面が持っている状態は、この 1 つだけ
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<span className="text-muted-foreground">ヘッダー</span>
<span className="font-semibold">
{isLoggedIn ? "さとうさん" : "ゲスト"}
</span>
</div>
<div className="rounded-md border px-3 py-2">
{isLoggedIn
? "会員向けの内容が表示されています"
: "ログインすると内容が表示されます"}
</div>
<Button size="sm" onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? "ログアウトする" : "ログインする"}
</Button>
</div>
);
}この章のまとめ
- 命令的 = 画面をどう変えるかの手順を書く
- 宣言的 = 状態と画面の対応関係を書く
- 対応を 1 か所書けば済むので、直し忘れによる食い違いが起きない
- React では「表示を変えたい」ではなく「状態を変えたい」と考える
login-view.tsx
"use client";
import { Button } from "@/components/ui/button";
import { useState } from "react";
export function LoginView() {
// 画面が持っている状態は、この 1 つだけ
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<span className="text-muted-foreground">ヘッダー</span>
<span className="font-semibold">
{isLoggedIn ? "さとうさん" : "ゲスト"}
</span>
</div>
<div className="rounded-md border px-3 py-2">
{isLoggedIn
? "会員向けの内容が表示されています"
: "ログインすると内容が表示されます"}
</div>
<Button size="sm" onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? "ログアウトする" : "ログインする"}
</Button>
</div>
);
}