表示とデータを切り分ける
このレッスンで分かること
- 作品データは 1 か所にまとめ、描画する関数はそれを読むだけにします
- データの形と画面に出す形は別物なので、間をつなぐ変換関数を 1 本置きます
- 変換関数は DOM をさわらないので、戻り値を見るだけで正しさを確かめられます
表示とデータを切り分ける とは
作品データを 1 か所に集め、画面に出す文字列を組み立てる仕事を独立した関数に切り出す設計です。データの出どころが変わっても描画側を書き直さずに済みます。
いま何が混ざっているか
入門で書いた createWorkCard をもう一度見ます。
function createWorkCard(work) {
const card = document.createElement("article");
card.className = "work-card";
if (work.views >= 100) {
card.classList.add("featured");
}
const heading = document.createElement("h3");
heading.textContent = work.title;
const meta = document.createElement("p");
meta.className = "work-meta";
meta.textContent = work.year + " 年 / " + work.views + " 回表示";
card.append(heading, meta);
return card;
}この関数は 2 つの仕事をしています。1 つは「views が 100 以上なら注目扱いにする」「年と表示回数を並べて 1 行にする」という 判断と組み立て。もう 1 つは createElement と append を呼ぶ DOM 操作 です。
前者はデータについての知識で、後者は画面についての知識です。この 2 つが同じ関数に入っていると、次の 3 つが起きます。
| 起きること | 具体例 |
|---|---|
| 確かめにくい | 表示文字列が正しいかを見るのに、実際に画面を描かせるしかない |
| 使い回せない | 同じ「年 / 回表示」の形を一覧表でも使いたいとき、コピーすることになる |
| 変更が波及する | データに category が増えると、DOM を組む関数まで開くことになる |
そこで、判断と組み立てだけを担う関数を 1 本切り出します。データを受け取り、画面に出したい値だけを持つ別のオブジェクトを返す関数です。このコースではこれを ビューオブジェクト と呼びます。
ここでやっているのは、道具を増やすことではなく仕事の境目を引き直すことです。関数が増えて行数は少し伸びますが、1 つの関数が答えるべき問いが 1 つになります。
データの形と表示の形は違う
作品データの形は第1章で決めたとおりです。
const work = {
id: 1,
title: "プロフィールサイト",
year: 2026,
views: 120,
published: true,
tags: ["HTML", "CSS"],
category: "site"
};一方、画面に出したいのは次のような値です。
const view = {
id: 1,
title: "プロフィールサイト",
meta: "2026 年 / 120 回表示",
categoryLabel: "サイト",
tagText: "HTML / CSS",
visible: true
};year と views は消えて meta という 1 本の文字列になりました。category は site という内部の値から サイト という日本語のラベルに変わりました。tags は配列のまま持たず、区切り文字でつないだ文字列にしてあります。published は visible という名前になりました。
名前が変わっている点が大事です。published は「作者が公開したかどうか」というデータの事実で、visible は「この画面に出すかどうか」という表示の判断です。いまは同じ値ですが、あとで「下書きも管理画面では見せる」となったときに、変わるのは visible のほうだけで済みます。
| 元の項目 | ビューの項目 | 変換のしかた |
|---|---|---|
year と views | meta | 年 / 回表示 の形につなぐ |
category | categoryLabel | 3 つの値を日本語のラベルに読み替える |
tags | tagText | 配列を区切り文字でつなぐ。未設定なら空文字 |
published | visible | 表示するかどうかの判断として読み替える |
値の読み替えは表で持つ
category から日本語のラベルを作るとき、if を 3 つ並べたくなります。値が 3 つのうちは動きますが、増えるたびに分岐が伸びます。オブジェクトを対応表として使うと 1 行で書けます。
const CATEGORY_LABELS = {
site: "サイト",
card: "カード",
table: "表"
};
console.log(CATEGORY_LABELS["site"]); // "サイト"
console.log(CATEGORY_LABELS["note"]); // undefined表に無い値を渡すと undefined が返ります。そのまま画面に出すと undefined という文字が並ぶので、|| で受け止めます。
const label = CATEGORY_LABELS[work.category] || "その他";同じ考えかたで、tags が未設定のときも受け止めます。work.tags が undefined のまま join を呼ぶと TypeError で止まるためです。
const tags = work.tags || [];
console.log(tags.join(" / ")); // 未設定なら ""
||は左が falsy なときに右を返します。空配列は truthy なので、tagsが[]のときは[]がそのまま使われ、joinの結果は空文字になります。狙いどおりです。
切り分けたあとの描画関数
変換関数ができると、DOM を組む側はこうなります。判断が 1 つも残っていない点に注目してください。
function createWorkCard(view) {
const card = document.createElement("article");
card.className = "work-card";
const heading = document.createElement("h3");
heading.textContent = view.title;
const meta = document.createElement("p");
meta.className = "work-meta";
meta.textContent = view.meta;
card.append(heading, meta);
return card;
}views >= 100 のような条件も、year と views をつなぐ処理も消えました。この関数がやるのは「渡された文字列を要素に入れる」ことだけです。データの形が変わっても、変換関数を直せばこちらは触らずに済みます。
呼び出す側は変換をはさむだけです。
for (const work of works) {
const view = toWorkView(work);
if (view.visible) {
workList.append(createWorkCard(view));
}
}第2章では、この for と if が filter と map の 2 行に変わります。切り分けが先に済んでいると、その置き換えが素直に通ります。
よくある間違い
- 変換関数の中で DOM をさわる — 1 行でも
documentが出てきたら、それはもう変換関数ではありません。戻り値だけで確かめられなくなります
// 誤り。変換のついでに画面も書き換えている
function toWorkView(work) {
document.querySelector("#count").textContent = "1 件";
return { title: work.title };
}- 未設定の項目をそのまま使う —
tagsを持たない作品が 1 件でもあると、そこで処理全体が止まります。第9章で API から取ってきたデータには、項目が欠けている行が混ざります
// 誤り。tags が undefined だと TypeError
const tagText = work.tags.join(" / ");
// 正しい
const tagText = (work.tags || []).join(" / ");- 元のデータを書き換えてしまう — 変換のついでに
work自体へ項目を足すと、元の配列が汚れます。新しいオブジェクトを作って返してください
// 誤り。引数のオブジェクトを書き換えている
function toWorkView(work) {
work.meta = work.year + " 年";
return work;
}要件
- 関数名は
toWorkView、引数は作品データのオブジェクトworkを 1 つだけ受け取る - 戻り値は
{ id, title, meta, categoryLabel, tagText, visible }の 6 項目を持つ新しいオブジェクトにする metaはwork.yearとwork.viewsから2026 年 / 120 回表示の形の文字列を組み立て、categoryLabelはsiteをサイト、cardをカード、tableを表に読み替える。それ以外と未設定はその他にするtagTextはwork.tagsを/でつないだ文字列にする。work.tagsが未設定のときは空文字を入れてエラーで止めない。visibleはwork.publishedがtrueのときだけtrueにする
入出力例
toWorkView({"category":"site","id":1,"published":true,"tags":["HTML","CSS"],"title":"プロフィールサイト","views":120,"year":2026}) → {"categoryLabel":"サイト","id":1,"meta":"2026 年 / 120 回表示","tagText":"HTML / CSS","title":"プロフィールサイト","visible":true}
toWorkView({"category":"card","id":2,"published":true,"tags":[],"title":"自己紹介カード","views":48,"year":2026}) → {"categoryLabel":"カード","id":2,"meta":"2026 年 / 48 回表示","tagText":"","title":"自己紹介カード","visible":true}
toWorkView({"category":"table","id":5,"published":false,"title":"学習ログ","views":0,"year":2026}) → {"categoryLabel":"表","id":5,"meta":"2026 年 / 0 回表示","tagText":"","title":"学習ログ","visible":false}
toWorkView({"category":"note","id":9,"published":true,"tags":["Idea"],"title":"つくりかけの計算機","views":7,"year":2025}) → {"categoryLabel":"その他","id":9,"meta":"2025 年 / 7 回表示","tagText":"Idea","title":"つくりかけの計算機","visible":true}