TypeScriptのコードを読んでいると、プロパティの後ろに?がついていたり、アクセスするときに?.を使っていたり、さらには!まで出てきて「これ全部同じ?じゃないの?」と混乱した方も多いのではないでしょうか。

?ってプロパティにつけるやつ?それとも?.のこと?
型定義の?とアクセスするときの?.って違うの?
!はどういうときに使うの?危なくないの?

この?という記号、実は使われる場所によってまったく意味が変わります。この記事では「TypeScriptのオプショナル(optional)とは何か」を軸に、?の2つの使い方と、セットで知っておきたい!の意味を整理します。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • interfaceやtypeのプロパティに?がついているのを見て意味がわからなかった方
  • ?.(オプショナルチェーン)と型定義の?の違いが気になっている方
  • !(Non-null assertion)って何?と思っている方
  • TypeScriptの?系の記号をまとめて整理したい方

この記事を読むと、TypeScriptで?が出てくる場面ごとの意味の違いと、関連する!の使いどころが整理できます。

それでは、順を追って詳しく見ていきましょう!

まずは動画解説を観る

TypeScriptのオプショナル(optional)とは — ?が使われる場面を整理する

まず、?がどんな場面で使われるかを先に確認しておきましょう。

記号 正式名称 使う場所 役割
? オプショナルプロパティ 型定義のプロパティ プロパティを省略可能にする
?. オプショナルチェーン 値へのアクセス時 nullやundefinedでもエラーを起こさずアクセスする

同じ?でも、型定義に書く?とアクセス時に書く?.はまったく別の記号です。これを混同すると、コードを読んでいても意味が追いにくくなります。

この2つを説明した後、?と対で覚えておきたい!(Non-null assertion)についても最後にまとめます。

オプショナルプロパティ — 型定義でプロパティを任意にする

?の中でも最もよく見かけるのが、型定義のプロパティにつける?(オプショナルプロパティ)です。

基本の書き方

interfaceやtypeでオブジェクトの型を定義するとき、プロパティ名の後ろに?をつけると「そのプロパティは省略してもよい」という意味になります。

interface User {
  name: string;     // 必須プロパティ
  age?: number;     // オプショナルプロパティ(省略可)
}

const user1: User = { name: "田中" };           // OK(ageを省略)
const user2: User = { name: "佐藤", age: 25 }; // OK(ageを指定)

age?: numberと書くことで、ageは「numberまたはundefined」どちらでもよいプロパティになります。?がついていないプロパティは省略できないため、nameを省略するとエラーになります。

省略するとundefinedになる

オプショナルプロパティを省略した場合、その値を参照するとundefinedが返ります。

interface User {
  name: string;
  age?: number;
}

const user: User = { name: "田中" };
console.log(user.age); // undefined

ageを省略しているので、user.ageundefinedになります。オプショナルプロパティを使うときは「undefinedが入ってくる可能性がある」と意識しておくことが大切です。

ユニオン型(| undefined)との違い

age?: numberage: number | undefinedは一見似ていますが、動作が微妙に異なります。

interface A {
  age?: number;        // 省略もOK、undefinedの明示もOK
}

interface B {
  age: number | undefined; // 省略は不可、値は必ず渡す必要がある
}

const a: A = {};               // OK
const b: B = {};               // NG(ageキー自体が必要)
const b2: B = { age: undefined }; // OK

age?: numberはプロパティのキー自体を省略できますが、age: number | undefinedはキーは省略できず、undefinedという値を明示的に渡す必要があります。

「どっちを使えばいいの?」と悩んだ場合、基本はage?: numberを使う方が素直です| undefinedとの細かい挙動の違いは、より詳細に扱う記事で解説しているので気になる方はそちらも参考にしてください。

なかむぅ
なかむぅ
nullやundefinedの型まわりをもっと詳しく知りたい方はこちらをどうぞ
【TypeScript】nullとundefinedの違いは?使い分けと安全に扱う方法
【TypeScript】nullとundefinedの違いは?使い分けと安全に扱う方法TypeScriptのnullとundefinedの違いを初心者向けに解説。使い分け方や型の書き方、エラーを防ぐ安全な扱い方までわかりやすく学べます。 ...

オプショナルチェーン(?.) — 安全にプロパティへアクセスする

型定義の?とは別に、値へのアクセス時に使う?.(オプショナルチェーン)があります。これは演算子なので、型定義とはまったく別の文脈で使います。

基本の使い方

オプショナルチェーン?.は、「アクセスしようとした値がnullやundefinedでも、エラーを起こさずにundefinedを返す」という挙動をします。

まず、オプショナルチェーンを使わない場合を見てみましょう。

interface User {
  address?: {
    city: string;
  };
}

const user: User = { /* addressなし */ };

// オプショナルチェーンなしだとエラーになる可能性がある
console.log(user.address.city); // TypeError: Cannot read properties of undefined

user.addressundefinedの場合、その後ろに.cityでアクセスしようとするとエラーが発生します。

?.を使うと、この問題を安全に回避できます。

console.log(user.address?.city); // undefined(エラーにならない)

?.は「左側がnullまたはundefinedなら、そこで評価を止めてundefinedを返す」という動作をします。エラーを起こさずに安全にプロパティへアクセスしたいときに使います。

??(Nullish Coalescing)と組み合わせる

?.を使ったアクセスがundefinedになったとき、代わりに使いたいデフォルト値を指定するのが??(Nullish Coalescing演算子)です。

const city = user.address?.city ?? "不明";
// addressがなければ "不明" が入る
console.log(city); // "不明"

??は「左側がnullまたはundefinedのときだけ右側の値を返す」演算子です。?.と組み合わせることで「取得できなかったときのデフォルト値」をシンプルに書けます。

||(OR演算子)も似たことができますが、||0や空文字""でも右側に切り替わってしまうのに対し、??はnullとundefinedのときだけです。この違いが気になったら覚えておいてください。

!(Non-null assertion) — 「nullじゃないよ」と伝える記号

最後に、?とは反対の意味を持つ!(Non-null assertion operator / 非nullアサーション)を解説します。

書き方と使う場面

TypeScriptは、値がnullやundefinedになる可能性があると判断した場合に警告を出します。そのとき「このケースでは絶対にnullじゃない」とTypeScriptに伝える記号が!です。

function getName(name: string | null): string {
  // TypeScriptは name が null の可能性があると判断してエラーを出す
  return name.toUpperCase(); // エラー: Object is possibly 'null'.
}

ここで!を使うと警告を消せます。

function getName(name: string | null): string {
  return name!.toUpperCase(); // OK(自分でnullじゃないと断言)
}

name!と書くことで「このnameはnullじゃないから、そのまま使わせて」とTypeScriptに伝えています。

よく使われる場面としては、DOMの要素取得などがあります。

const button = document.querySelector<HTMLButtonElement>("#btn");
button!.addEventListener("click", () => console.log("クリック"));
// querySelector の戻り値は null の可能性があるが、
// 「このIDのボタンは絶対存在する」と自分で分かっているとき

使いすぎには注意が必要

!は便利に見えますが、乱用するとせっかくTypeScriptが守ってくれている型安全性を自分で壊すことになります。

function getName(name: string | null): string {
  return name!.toUpperCase(); // もし name が実際に null だったら
                              // 実行時にエラーが発生する
}

getName(null); // RuntimeError: Cannot read properties of null

TypeScriptはコンパイル時にチェックしてくれますが、!を使うと「ここはチェックしなくていい」と伝えることになります。実際にnullが入ってきた場合は、ランタイムエラーになります。

!を使う前に、まずif文や?.でチェックできないかを考えるクセをつけましょう。

// ! を使わないアプローチ
function getName(name: string | null): string {
  if (name === null) return "名前なし";
  return name.toUpperCase(); // TypeScriptが「ここは string だ」と判断してくれる
}

このように「nullのケースを先に分岐で除外する」方が、型安全なコードになります。!はどうしても代替手段がないときの最終手段として考えておきましょう。

【付録】さらに学びを深めるためのリソース


さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。

おすすめの書籍

ゼロからわかる TypeScript入門


技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。

変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。

プロを目指す人のためのTypeScript入門


技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。

オンラインで参照できる公式ドキュメント

TypeScript公式ハンドブック


https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。

TypeScript Deep Dive


https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。

TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。


まとめ — ?・?.・!の使い分けを一覧で確認

今回解説した3つの記号をまとめます。

記号 名称 使う場所 役割 注意点
? オプショナルプロパティ 型定義のプロパティ プロパティを省略可能にする 省略するとundefinedになる
?. オプショナルチェーン アクセス演算子 nullやundefinedでもエラーを防ぐ エラーは防ぐが、undefinedが返る点は意識する
! Non-null assertion 値の直後 「nullじゃない」とTypeScriptに伝える 乱用すると型安全性を自分で壊すことになる

大事なのは、?は「型定義の記号」と「アクセス演算子」で別物だという区別です。型定義の?と演算子の?.を混同してしまうと、コードを読み書きするときに混乱の原因になります。

また、!は「TypeScriptへの約束」です。使う際は「本当にここはnullじゃないか」を自分で確認してから使うようにしましょう。

interfaceとtypeの基本的な書き方が気になった方はこちらもどうぞ。

なかむぅ
なかむぅ
interfaceとtypeの基本的な使い方はこちらから確認できます
TypeScriptのinterfaceとは?オブジェクト型定義の基本と使い方TypeScriptのinterfaceの基本から使い方、typeとの違いまで初心者向けにわかりやすく解説します...
なかむぅ
なかむぅ
null/undefinedまわりをもっとしっかり理解したい方はこちらもどうぞ
【TypeScript】nullとundefinedの違いは?使い分けと安全に扱う方法
【TypeScript】nullとundefinedの違いは?使い分けと安全に扱う方法TypeScriptのnullとundefinedの違いを初心者向けに解説。使い分け方や型の書き方、エラーを防ぐ安全な扱い方までわかりやすく学べます。 ...

※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。