宣言的UIと命令的UIとは?コードで見る違い
はじめに
宣言的UIを理解するためにまずは「宣言」という言葉から見ていこうと思います。
さらに理解を深めるために宣言的UIが登場する前に使われていた命令的UIを説明します。宣言的UIは命令的UIの問題点を解決するために登場したものなので、両者の違いを見ると宣言的UIの素晴らしさが伝わると思います。
宣言という言葉の意味から宣言的UIを考えてみる
Googleで検索したところ次のような言葉の定義が出てきました。
広く一般にむかっていうこと。個人や団体などが、その意思・意見・方針などを、広く外部に表明すること。
これだと少し大げさなので上記のフォーマットをあまり崩さずに少し言葉を換えます。
コードを読むすべての人に対して、マークアップがどういったUIになるか表明すること
つまり、マークアップを見ただけで状態の変化も含めてどういったUIになるかわかるようなUIを宣言的UIといいます。
Reactで見る宣言的UI
宣言的UIを実現するためにReactではUIの変化する部分をstateという変数を使って定義します。
import { useState } from "react";
import "./styles.css";
export default function App() {
const [count] = useState(0);
return (
<div className="App">
<h1>カウントアプリ</h1>
<p>{count}</p>
</div>
);
}
UIは以下のようになります。
前項で書いた通り、実際にブラウザに表示されるUIがマークアップと一致しています。動的に変化する部分はcount変数にすることで判断できますね。
ではこのカウント0から1に変化させるとUIの構造はどうなるでしょうか。
useStateのset関数をつかってカウントアップを実装します。
export default function App() {
const [count, setCount] = useState(0);
return (
<div className="App">
<h1>カウントアプリ</h1>
<p>{count}</p>
<button type="button" onClick={() => setCount((c) => c + 1)}>
count up
</button>
</div>
);
}
カウントアップを実装したことでボタンが増えました。
ボタンを押下するとsetCountが呼ばれてcount変数が更新されます。3回押すと以下のようになりました。
カウントアップを実装してもマークアップとUIが一致しており、マークアップからどのようなUIになるか想像ができます。
あれでも関数がUIに入ってないか?と思うかもしれません。これは分けて書くことができます。
export default function App() {
const [count, setCount] = useState(0);
// UIから分離した
const handleCountupClick = () => {
setCount((c) => c + 1);
};
return (
<div className="App">
<h1>カウントアプリ</h1>
<p>{count}</p>
<button type="button" onClick={handleCountupClick}>
count up
</button>
</div>
);
}
このように宣言的UIを実現するためにReactでは、UIとロジックのつながりがほとんどない状態となっており、ReactがUIを直接操作することはありません。UIが状態の変化を監視してUIを変化させています。
(useRefを使ってDOM操作することがありますが、あまり多くないと思います。forcusしたりscrollするときに使うイメージです。詳細はReactの公式Docsを参照してみてください。)
Javaで見る宣言的UI
Javaでも宣言的なUIが見られます。JavaではThymeleafというツールを使ってHTMLの中身をJavaで書き換えます。HTMLを次のように書きます。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<body>
<h1>カウントアップ</h1>
<p th:text="${count}"></p>
</body>
</body>
</html>
かなりReactに似ていますね。
命令的なUI
例えば以下のような命令的UIがあります。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form id="form">
<label>First name:
<b id="nameText">Jane</b>
<input id="nameInput" value="Jane">
</label>
<button type="submit" id="editButton">Edit Profile</button>
<p>
<i id="helloText">Hello, Jane</i>
</p>
</form>
</body>
</html
UIの更新ロジックは以下の通りです。
function handleFormSubmit(e) {
e.preventDefault();
if (editButton.textContent === 'Edit Profile') {
editButton.textContent = 'Save Profile';
hide(nameText);
show(nameInput);
} else {
editButton.textContent = 'Edit Profile';
hide(nameInput);
show(nameText);
}
}
function handleNameChange() {
nameText.textContent = nameInput.value;
helloText.textContent = (
'Hello ' +
nameInput.value + '!'
);
}
function hide(el) {
el.style.display = 'none';
}
function show(el) {
el.style.display = '';
}
// DOM要素の取得
let form = document.getElementById('form');
let editButton = document.getElementById('editButton');
let nameInput = document.getElementById('nameInput');
let nameText = document.getElementById('nameText');
let helloText = document.getElementById('helloText');
form.onsubmit = handleFormSubmit;
nameInput.oninput = handleNameChange;
なんとなくわかりづらいのが伝わるでしょうか。
一旦、コードを読んでみて何をしているコードなのか予想してみてください。
・
・
・
ではこれをReactで書き直してみます。
import { useState } from "react";
export default function App() {
const [name, setName] = useState("");
const [edit, setEdit] = useState(true);
const handleNameChange = (e) => {
setName(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
setEdit((e) => !e);
};
return (
<form onSubmit={handleSubmit}>
<label>
First name:
{edit ? (
<input value={name} onChange={handleNameChange} />
) : (
<b>{name}</b>
)}
</label>
<button type="submit">Edit Profile</button>
<p>
<i id="helloText">{`Hello, ${name}`}</i>
</p>
</form>
);
}
Reactのほうがコード量が少なく、かつ読みやすいですね。
※以下のような要件のアプリでした。
・名前を入力すると「Hello <入力した名前>!」が表示される
・Edit Profileボタンを押下すると入力フォームが消えて、入力した名前を太字で表示する
命令的UIと宣言的UIの違いはなんでしょうか。命令的UIは宣言的UIと同じようにマークアップとUIが一致しているように見えます。しかし宣言的UIと違って命令的UIでは、どこが変化するのかがわからないので、マークアップが最終的にどういったUIになるのかわかりません。
命令的UIにおける変更では、ロジックだけではなくUIに対しても細かく命令を出していく必要があります。なぜかというとReactではuseState APIの戻り値を使って動的に変化する部分をUIに埋め込むことができました。さらに変更を加えるためのイベントハンドラもUIの中に定義できました。
命令的UIでは上記のような仕組みが存在しないため次のようにコードを書いていく必要があります。
- 変更したいタグを取得する
- タグに対してイベントハンドラを指定する
- イベントハンドラを定義する。
- イベントハンドラ内で処理をして、1で取得したタグに値をセット
このようにUIに対して、変更が入るたびに「この値を表示して」と直接命令していくようなUIを命令的UIといいます。
命令的UIではUIが状態を監視していないため、状態の参照・計算・更新をしてさらにUIにそれを伝えないといけません。宣言的UIと違って、状態とUIどちらにも同じような命令の処理をするため、簡単なことをする場合でも複雑になってしまいます。
宣言的UIの図と比べるとロジックから双方向の矢印がUIと状態に向けられています。
まとめ
・宣言的UIとは、コードを読むすべての人に対して、マークアップがどういったUIになるか表明すること(マークアップを見ただけで状態の変化も含めてどういったUIになるかわかるUI)
・Reactでは動的に変化する部分をuseStateを使って表現することで宣言的UIにしている
・宣言的UIではUIは状態を見ているだけ。ロジックはUIを直接変更しない。
・命令的UIでは、変化させる部分をいちいち指示を出さなくてはならない。
Discussion