コース目次 / 第2章

オブジェクトと型

ひとまとまりのデータをオブジェクトで表し、その形を type で宣言します。

第2章 / 全11章目安 約85分この章のゴール: 行き先カードが3枚並び、綴り間違いが赤線で止まるようになる

経験者の方へ: JavaScriptに自信がある方は、Aパートは流し読みで構いません。事故が起きるところと、TSにするところから精読してください。

今日のゴール

行き先カードが3枚並びます(直島・台北・パリ)。

今日つかまえるバグ: place.nameplace.nmae と打ち間違える事故。JavaScriptはこれを undefined と表示して終わります。

ミニ解説その1(JS): オブジェクト

いくつかの値をひとまとまりにして持ちたいことがあります。「直島・日本・2泊」はバラバラの3つではなく、1つの行き先です。これを表すのが オブジェクト です。

const place = { name: '直島', country: '日本', nights: 2 };
console.log(place.name); // 直島

{ 名前: 値, ... } の形で書き、.名前 で取り出します。{} はオブジェクト、[] は配列です。ここは混ざりやすいので、箱の図で覚えてください。

  • [] … 順番に並んだ箱。「1番目、2番目…」
  • {} … 名札のついた箱。「name、country…」

そして map。コース1で使った forEach は「1個ずつ実行するだけ」でしたが、map1個ずつ変換して、新しい配列を返します。カードのHTMLを作るのにぴったりです。

Aパート: JSで書く

新しいファイル src/places.js を作ります。このコースで初めて、自分でファイルを増やします。

src/places.js
export const places = [
  { name: '直島', country: '日本', nights: 2, lat: 34.46, lon: 133.99, currency: 'JPY' },
  { name: '台北', country: '台湾', nights: 4, lat: 25.04, lon: 121.56, currency: 'TWD' },
  { name: 'パリ', country: 'フランス', nights: 5, lat: 48.86, lon: 2.35, currency: 'EUR' },
];

export は「このファイルの外から使ってよい」という印です。受け取る側は import します。

src/main.ts からカードを描きます。

src/main.ts+1 / -0
 import './style.css';+import { places } from './places';  const TRIP_DATE = '2026-09-19';

行頭の + - は変更の目印です。コピーすると、記号と削除された行を除いたこの章を終えた時点の内容が入ります。

src/main.ts+12 / -0
 const greeting = document.querySelector('#greeting')!; greeting.textContent = greet('はると', daysUntil(TRIP_DATE));++const placesBox = document.querySelector('#places')!;+placesBox.innerHTML = places+  .map(+    (place) => `+      <article class="place">+        <h3>${place.name}</h3>+        <p>${place.country} / ${place.nights}泊</p>+      </article>+    `,+  )+  .join('');

行頭の + - は変更の目印です。コピーすると、記号と削除された行を除いたこの章を終えた時点の内容が入ります。

保存すると、カードが3枚出ます。ここまでがAパートです。

自分のファイルを読み込むときは必ず ./ から書きます(‘./places’)。./ を忘れると「そんな部品はどこにも無い」とViteが赤い画面で怒ります。

innerHTML についてのひとこと。 ここでは innerHTML に文字列を組み立てて入れています。自分で書いたデータだけを表示するうちは、これで問題ありません。
ただし、他人が入力した文字を混ぜて表示するアプリでは話が変わります。文字の中にHTMLタグが混ざっていると、それがそのまま画面の一部として動いてしまうからです(XSSと呼ばれる事故です)。そういうアプリでは textContent を使うか、記号を無害な文字に置き換えてから入れます。「自分のデータだけならinnerHTML、人のデータが混ざるならtextContent」と覚えておいてください。

事故を踏む

${place.name}${place.nmae} に変えて保存してください。

画面のカードの見出しが undefined になります。開発サーバは止まりません。JavaScriptは「そんな名札の箱は無いね」と言うかわりに、静かに undefined を返して先へ進みます。第1章で覚えた「実行する係(Vite)は検査しない」がそのまま出ています。

いっぽう検査する係は、もう気づいています。 npm run check を打つと、この時点でこう出ます。

npm run check の出力
src/main.ts(25,21): error TS2339: Property 'nmae' does not exist on type '{ name: string; country: string; nights: number; lat: number; lon: number; currency: string; }'.

「まだ型を書いていないのに、なぜ分かるの?」
tsconfig.json に書いた allowJs のおかげで、TypeScriptは places.js の中身も読んでいます。データがそこに直接書いてあるので、形をその場で推し量ってくれているのです。
ただし、エラー文をよく見てください。型の名前が出てこず、{ name: string; country: string; … } というその場ででっち上げた形が並んでいます。今日やるのは、この形に Place という名前を付けることです。名前が付くと、エラー文も補完も、ぐっと読みやすくなります。

ミニ解説その2(TS): 型の世界と値の世界

ここから先ずっと使う地図を1枚。

2レーンの地図
値の世界(実行時に本当に存在するもの)
  const places = [...]        ← 実際のデータ
  function greet(...) {...}   ← 実際の処理

型の世界(検査のときだけ存在するもの)
  type Place = {...}          ← 形の宣言
  : string  : number注釈

  ビルドすると型の世界はまるごと消える

type型の世界の住人です。値を入れることはできません。

Bパート: TSにする

places.jsF2places.ts にリネームします。呼び出し側(main.ts)は 1文字も直す必要がありません。拡張子を書かずに './places' と読み込んでいるからです。

places.js → places.ts型だけの変更+10 / -1
-export const places = [+export type Place = {+  name: string;+  country: string;+  nights: number;+  lat: number;+  lon: number;+  currency: string;+};++export const places: Place[] = [   { name: '直島', country: '日本', nights: 2, lat: 34.46, lon: 133.99, currency: 'JPY' },   { name: '台北', country: '台湾', nights: 4, lat: 25.04, lon: 121.56, currency: 'TWD' },   { name: 'パリ', country: 'フランス', nights: 5, lat: 48.86, lon: 2.35, currency: 'EUR' }, ];

行頭の + - は変更の目印です。コピーすると、記号と削除された行を除いたこの章を終えた時点の内容が入ります。

足したのは type Place = {...}: Place[] だけで、データの行は1行も動いていませんPlace[] は「Placeが並んだ配列」という意味です。

そして place.nmae をもう一度やってみてください。エラー文が変わります。

TS化したあとの同じ間違い
src/main.ts(25,21): error TS2339: Property 'nmae' does not exist on type 'Place'.

さっきの長い { name: string; country: string; ... } が、Place のひとことになりました。しかも place. まで打つと 候補が出ます

型を書く前と後で、守られ方は同じです。変わったのは読みやすさと、道具としての使いやすさ。そして次の章から、この Place という名前を関数の引数や戻り値に書けるようになります。名前が付いたものは、持ち運べます。

型は防具であると同時に道具です。守ってくれるだけでなく、補完で手を貸してくれます。ここが「型を書くと楽になる」の入口です。

コラム: interface という書き方

世の中のコードでは type のかわりに interface をよく見ます。

読めればOK
interface Place {
  name: string;
  country: string;
}

いまは「同じようなものだ」と思っておいてください。 このコースは type に統一します(第4章で使うユニオンは type でしか書けないため)。違いは第10章で正式に読みます。

読み替えの約束(この章の隠れた主役)

これから毎章、自分の内容に書き換えていきます。すると教材の差分と自分のコードが少しずつずれます。そこで約束を1つ。

値(行き先の名前・数字・文言)は自由に変えてください。
型名・フィールド名・関数名は、見本と同じままにしてください(Placenamenights など)。
自分だけの項目を足すときは // mine とコメントを付けてください。

こうしておくと、以降の章の差分を「名前」で自分のコードと突き合わせられます

わざと壊してみる

places.ts の1件目から country の行を消してください。ここで赤くなるのが正解です。 Property 'country' is missing。型は「揃っているべきものが欠けている」ことも見つけます。確認したら戻します。

あなたの番

  • 自分の行きたい場所3つに書き換えてください。コース1の「行きたい場所メモ」を作った方は、そこから持ってきてください。
  • 緯度経度(lat / lon)は、地図サイトで場所を右クリックすると数字が出ます。第5章と第9章で本当に使いますので、だいたいで構いませんが入れておいてください。
  • 表示する項目を1つ増やしてみてください。 type に足す → データに足す → 描画に足す、の3段です。自分だけの項目には // mine を付けます。
  • 合格条件: 変えたあとも npm run check がエラー0。

実務メモ

React Nativeの FlatList に渡す配列と、その要素の型。それはまさに今日の Place[] です。

TSエラー語辞典

この章で実際に出るエラー英文と、その日本語訳です。卒業するころには全章ぶんがお助けページに集まります。

エラー英文 言っていること
Property 'nmae' does not exist on type 'Place' Placeにnmaeという持ち物はありません。綴りを見てください
Cannot find module './places' その場所にファイルが見つかりません。./ を忘れていませんか

ハマったら

Viteが赤い画面でエラーを出した

読み込みに失敗したときの画面です。上から2行目のファイル名と行番号だけ見れば、たいてい原因が分かります。多いのは ./ の書き忘れと、ファイル名の綴り違いです。

ZIPで戻したい

このページの下には2つのZIPがあります。JS版はAパート(JSで動いた状態)まで、TS版は章末(TS化まで終わった状態)です。詰まった場所に合わせて選んでください。

解凍したら、フォルダの中で次の2つを順に打ちます。

ZIPを解凍したフォルダの中で
npm installnpm run dev

行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。

丸ごと置き換えると自分で書き換えた内容が消えます。 まず src/places.ts(自分の行き先データ)と、自分で変えた文言をメモ帳に待避してから、壊れたファイルだけを差し替えてください。

こうなっていればOK

うまくいかないときは、この章のファイル一式をダウンロードして続きから進めても大丈夫です。JS版(TS化する前)TS版(章末)の2つがあります。

あと4章で、あなたのサイトに公開URLが付きます。

この章はまだ完了していません。