| title | React |
|---|
import createReactProjectVideo from "./create-react-project.mp4"; import componentRenderingVideo from "./component-rendering.mp4";
これまで、JavaScriptによりHTML要素を操作するために、DOMを用いることができることを学んできました。しかしながら、ナイーブな方法によりDOMを使用すると、アプリケーションの規模の限界がすぐにやってきます。
簡単なToDoアプリケーションを例に考えてみましょう。
<ul id="todos"></ul>
<input id="message" />
<button id="add-todo" type="button">追加</button>const todoContainer = document.getElementById("todos");
const messageInput = document.getElementById("message");
const addTodoButton = document.getElementById("add-todo");
addTodoButton.onclick = () => {
const message = messageInput.value;
const li = document.createElement("li");
const span = document.createElement("span");
span.textContent = message;
li.appendChild(span);
const removeTodoButton = document.createElement("button");
removeTodoButton.type = "button";
removeTodoButton.textContent = "削除";
removeTodoButton.onclick = () => {
todoContainer.removeChild(li);
};
li.appendChild(removeTodoButton);
todoContainer.appendChild(li);
};なんとか作り上げることができましたが、このまま要件を増やして複雑なプログラムを作ろうとすれば、要素の作成忘れ、削除忘れなどにより、すぐに破綻してしまいそうです。
このようになってしまう根本的な原因は、現在の状態がDOMに記憶されてしまっていることにあります。DOMには、テキストや位置、色など、数えきれない状態が格納されています。状態の数が$n$種類あれば、その遷移パターンは$n^2$個になるわけですので、状態の数が増えることが非常に危険であることは明らかです。
ところが、アプリケーションの本質的な状態というのは、一般的にそこまで多いものではありません。例えば、ToDoリストアプリケーションであれば、各ToDoを表すstringの配列string[]がひとつだけあれば、アプリケーションの状態は全て表現できていることになるはずです。
宣言的UIは、こういった性質に着目します。より具体的に説明するのであれば、アプリケーションの状態$S$に対し、関数$f(S)$によりUIの状態を表現できるのであれば、開発者の関心を$S$の変化と$f$の定義のみに絞ることができるというわけです。
具体的なコードで確認してみましょう。先ほどのToDoアプリケーションを、宣言的UIのアプローチを用いて書き換えてみましょう。状態を追いやすいよう、TypeScriptを用いて記述します。
まずはアプリケーションの状態と、その状態を格納する変数を宣言します。
type State = { todos: string[] };
const state: State = { todos: [] };続いて、state変数をもとにUIを構築する関数renderを定義します。
function render() {
// いったん既存の要素を全て削除
todoContainer.innerHTML = "";
for (const todo of state.todos) {
const li = document.createElement("li");
// ここにliの中身を作る処理が入る
todoContainer.appendChild(li);
}
}最後に、状態を変化させる関数を定義します。状態を変化させたら、render関数を呼び出して、変更をUIに反映させます。
function addTodo(todo: string) {
state.todos.push(todo);
render();
}
function removeTodo(index: number) {
state.todos.splice(index, 1);
render();
}これにより、アプリケーション全体の状態が変数stateに集約され、開発者が意識すべき状態のパターンを大幅に減らすことに成功しました。
先ほどのプログラムはうまく動作しますが、一つ問題があります。それは、render関数が呼ばれるたびに全ての要素が削除され、再構築される点です。一般的に、DOMに対する操作は非常にコストが高く、可能な限り減らすことがパフォーマンス改善のために有効です。
Reactは、この問題を仮想DOMを用いて解決します。Reactは、DOMに似たデータ構造を内部的にJavaScriptオブジェクトの形式で保持し、実際に変更された部分のみを実際のDOMに反映させることで、高いパフォーマンスを実現しています。
それでは、Reactを用いたプロジェクトを作成してみましょう。Viteでプロジェクトを作成しますが、frameworkはReact、variantはTypeScriptを選択してください。