表示とデータを切り分ける

このレッスンで分かること

  • 作品データは 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 つは createElementappend を呼ぶ 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 };

yearviews は消えて meta という 1 本の文字列になりました。categorysite という内部の値から サイト という日本語のラベルに変わりました。tags は配列のまま持たず、区切り文字でつないだ文字列にしてあります。publishedvisible という名前になりました。

名前が変わっている点が大事です。published は「作者が公開したかどうか」というデータの事実で、visible は「この画面に出すかどうか」という表示の判断です。いまは同じ値ですが、あとで「下書きも管理画面では見せる」となったときに、変わるのは visible のほうだけで済みます。

元の項目ビューの項目変換のしかた
yearviewsmeta年 / 回表示 の形につなぐ
categorycategoryLabel3 つの値を日本語のラベルに読み替える
tagstagText配列を区切り文字でつなぐ。未設定なら空文字
publishedvisible表示するかどうかの判断として読み替える

値の読み替えは表で持つ

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.tagsundefined のまま 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 のような条件も、yearviews をつなぐ処理も消えました。この関数がやるのは「渡された文字列を要素に入れる」ことだけです。データの形が変わっても、変換関数を直せばこちらは触らずに済みます。

呼び出す側は変換をはさむだけです。

for (const work of works) { const view = toWorkView(work); if (view.visible) { workList.append(createWorkCard(view)); } }

第2章では、この foriffiltermap の 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; }

要件

  1. 関数名は toWorkView、引数は作品データのオブジェクト work を 1 つだけ受け取る
  2. 戻り値は { id, title, meta, categoryLabel, tagText, visible } の 6 項目を持つ新しいオブジェクトにする
  3. metawork.yearwork.views から 2026 年 / 120 回表示 の形の文字列を組み立て、categoryLabelsiteサイトcardカードtable に読み替える。それ以外と未設定は その他 にする
  4. tagTextwork.tags/ でつないだ文字列にする。work.tags が未設定のときは空文字を入れてエラーで止めない。visiblework.publishedtrue のときだけ 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}

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.js
main.js
学習モード

メモ

表示とデータを切り分ける

⌘S で保存