JavaScript:設計(状態→描画の形を作る)
音声
目次
state(状態)を1か所に集める
今回できるようになることとして、状態を一か所にまとめ、状態を基準に画面を更新する設計ができるようになります。
アプリの状態(state)は入力値や一覧、チェック状態などを含む単一のオブジェクトにまとめると管理が楽になります。
CodePenやブラウザで試す場合、stateはJS側に置いておき、HTMLは描画先だけ用意します。
状態を分散させると同期が取れずバグになりやすいため、まずは1つに集約する癖をつけます。
render(描画)を1本にする
画面への反映はrender関数を1つにまとめて実装します。
renderはstateを参照してDOMを作り直す役割だけに絞ると分かりやすいです。
複数箇所でDOM更新ロジックをばらばらに書くと整合が取れなくなります。
renderはstateの「読み取り専用」の関数にして、イベントで状態変更が起きたら必ずrenderを呼ぶ、というルールを守ると画面の一貫性が保てます。
handler(イベント)→ state更新 → render
イベントハンドラはイベントの受け取りとstate更新だけを担当し、更新後にrenderを呼び出します。例えばボタン押下でリストに項目を追加する場合、ハンドラで新しいstateを作って代入し、最後にrenderを1回呼ぶ流れです。イベントバインドは初期化時(DOMContentLoadedやDOMContentLoaded相当)にまとめて行うと、どこでハンドラが登録されているか分かりやすくなります。
最小コード:
貼り付け場所:CodePen想定です。HTML欄にHTML、JS欄にJavaScriptを貼ってください。
<!-- HTML欄 -->
<input id="todoInput" placeholder="項目を入力">
<button id="addBtn">追加</button>
<div id="list"></div>
// JS欄
// state = 画面の「元データ」を1か所に集約する(DOMから状態を読み取らない方針)
let state = { todos: [] };
// render = state → 画面(DOM) への変換を「1本化」する場所
function render() {
// 描画先(ここだけDOMを触るルールに寄せる)
const out = document.getElementById('list');
// stateからHTML文字列を作る(DOMを逐次いじらず、まとめて作って入れ替える)
// data-id に「どのtodoか」を持たせる(クリック時にstate更新へつなぐため)
out.innerHTML = state.todos.map(t => `
<div data-id="${t.id}">
<input type="checkbox" ${t.done ? 'checked' : ''}>
${t.text}
</div>`
).join('');
// 描画し直すと要素が作り直されるので、イベントも貼り直す必要がある
attachHandlers();
}
// handler側の処理:stateを更新して、最後にrender()を呼ぶだけにする
function addTodo(text) {
// 破壊的変更を避ける:新しいstateを作って「差し替える」
// todosも新配列にして「追加」を表現する
state = {
...state,
todos: [...state.todos, { id: Date.now(), text, done: false }]
};
// 画面更新は必ず render 経由(ここが設計の芯)
render();
}
function toggleTodo(id) {
// mapで「対象だけ」doneを反転した新配列を作る(元の配列は触らない)
state = {
...state,
todos: state.todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t
)
};
// stateが変わったら render(DOM更新ロジックは散らさない)
render();
}
function attachHandlers() {
// render後に作られたDOMに対してイベントを結び直す
document.querySelectorAll('#list div').forEach(el => {
// checkbox クリック → data-id を取り出す → state更新(toggleTodo)へ
el.querySelector('input').onclick = () =>
toggleTodo(Number(el.dataset.id));
});
}
// 「追加」ボタンのイベント:やることは入力取得 → state更新関数を呼ぶだけ
document.getElementById('addBtn').onclick = () => {
const v = document.getElementById('todoInput').value.trim(); // 入力の前後空白を除去
if (v) addTodo(v); // 空なら何もしない(変な空項目を防ぐ)
};
// 初回表示:state(空)を元にまず描画して、以降は state→render の流れで回す
render();
破壊的変更(mutate)を減らす
stateを直接書き換える(破壊的変更)はバグの原因になりやすいです。
配列やオブジェクトを更新するときはスプレッド構文やmap/filterで新しい値を作ると履歴が追いやすくなります。
例えば配列の要素を切り替えるときはmapで新配列を返し、追加はスプレッドで新しい配列を作り、削除はfilterで作る方針が分かりやすいです。
イミュータブルな更新は意図しない共有参照を防ぎ、renderの信頼性も高めます。
部品化の考え方(Reactの入口)
画面を小さな部品(コンポーネント)に分け、各部品がstateの一部を受け取って描画するイメージを持つと成長が早いです。
最初は関数でDOM片を返すだけでも十分です(props相当を引数にします)。
Reactはこの考えを徹底しているライブラリなので、今回の設計に慣れるとReactの学習がスムーズになります。
部品化は再利用とテストを楽にします。
実行方法:ブラウザで開くか、CodePenのRunで実行します。Consoleで結果を見ます。
確認方法:console.logの出力や画面表示が想定どおりかで確認します(必要なら簡単な条件チェックもします)。
よくあるミス:Consoleを開いていないため出力が見えない、または変数名のスペル違いでReferenceErrorになることがあります。
使い分けの基準:繰り返しはfor/for...of、変換はmap、絞り込みはfilterのように目的で使い分けます。
注意(ここだけ)
stateを複数箇所に分散させると不整合が起きます。まずは「1か所に集約」が基本です。
stateを直接書き換える(破壊的変更)と、差分追跡が難しくなります。コピーして更新する流れを徹底します。
renderがあちこちに散ると表示が崩れます。UI更新は「render 1本」に寄せると安全です。
要約
- stateは1か所に集めると同期が取りやすくなります。
- 画面更新はrenderを一本化して責務を分けます。
- イベントはハンドラでstateを更新し、更新後にrenderを呼びます。
- 破壊的変更を避け、スプレッドやmap/filterで新しい値を作ると安全です。
- 小さな部品に分ける考え方はReactの学習につながります。