TypeScriptのコードを見ていると、?
や!
といった記号をよく見かけます。
でも、「この記号って何をしてるの?」と思ったことはありませんか?
この記事では、TypeScriptでよく使われる以下の記号について、それぞれの使い方や注意点をサンプルコードを用いてわかりやすく解説します。
?
(オプションパラメーター・オプショナルチェーン)??
(Null合体演算子)??=
(Null合体代入演算子)!
(非nullアサーション演算子・明確な割り当てアサーション)
「?」に関する機能と使い方
?
には、以下のような機能があります。それぞれの用途や使い方について、順番にわかりやすく解説していきます。
- オプションパラメーター(
?
)- 関数の引数を省略可能にし、指定されなかった場合は
undefined
として扱う記号
- 関数の引数を省略可能にし、指定されなかった場合は
- オプショナルチェーン(?.)
- オブジェクトのプロパティにアクセスする際、途中で
null
やundefined
があっても安全にアクセスできる記号
- オブジェクトのプロパティにアクセスする際、途中で
- Null合体演算子(
??
)- 左辺の値が
null
またはundefined
のときに、右辺のデフォルト値を返す演算子
- 左辺の値が
- Null合体代入演算子(
??=
)- 変数の値が
null
またはundefined
のときだけ、右辺の値を代入する演算子
- 変数の値が
オプションパラメーター(?)
オプションパラメーター(?
)は、関数の引数を省略可能にするための記号です。
通常、TypeScriptでは関数に定義されたすべての引数を渡す必要があります。しかし、引数にオプションパラメーター(?
)を付けることで、その引数が省略可能になります。省略した場合は、undefined
として扱われます。
オプションパラメーター(?
)を使用しない場合と使用した場合の動作をサンプルコードで確認してみましょう。
オプションパラメーター(?)を使用しない場合
function greet(name: string, age: number) {
console.log(`${name} is ${age} years old.`);
}
greet('Taro'); // エラー:Expected 2 arguments, but got 1.
オプションパラメーター(?
)を使用しない場合、引数age
を省略してgreet
関数を呼び出すと、「引数が足りません」というエラーが発生します。
オプションパラメーター(?)を使用した場合
function greet(name: string, age?: number) {
console.log(`${name} is ${age} years old.`);
}
greet('Taro'); // Taro is undefined years old.
greet('Hanako', 25); // Hanako is 25 years old.
引数age
にオプションパラメーター(?
)をつけることで、引数age
を省略してgreet
関数を呼び出しても、エラーにならなくなります。引数を省略した場合は、undefined
となっていることも確認できます。
オプションパラメーター(?)の注意点
オプションパラメーター(?
)を使うと、その引数の型は「型 | undefined
」のようなユニオン型になります。
function greet(name: string, age?: number) {
// ageは number | undefined になる
console.log(`${name} is ${age} years old.`);
}
また、オプションパラメーター(?
)は、必ず引数の「最後」に書く必要があります。
function greet(age?: number, name: string) {
console.log(`${name} is ${age} years old.`);
}
// エラー:A required parameter cannot follow an optional parameter.
必須パラメーターの前にオプションパラメーター(?
)があると、TypeScriptはエラーを出します。
オプショナルチェーン(?.)
オプショナルチェーン(?.
)は、オブジェクトのプロパティアクセス時にnull
やundefined
の可能性がある場合でも、安全にアクセスできる記号です。
通常、TypeScriptでは、オブジェクトの中にある深いプロパティを参照するときに、どこかの階層がnull
やundefined
だとエラーになります。しかし、オプショナルチェーン(?.
)を使うと、途中のプロパティがnull
やundefined
のときには、エラーを出さずにundefined
を返してくれます。
オプショナルチェーン(?.
)を使用しない場合と使用した場合の動作をサンプルコードで確認してみましょう。
オプショナルチェーン(?.)を使用しない場合
type User = {
name: string;
contact?: {
email: string;
};
};
const user: User = {
name: 'Yamada',
};
const email = user.contact.email; // エラー:'user.contact' is possibly 'undefined'.
console.log(email);
contact
が存在しない可能性があるにもかかわらず、user.contact.email
と直接アクセスしようとすると、TypeScriptは「contact
がundefined
かもしれないのに、その中のemail
を読もうとしている」と判断し、エラーを出します。
オプショナルチェーン(?.)を使用した場合
type User = {
name: string;
contact?: {
email: string;
};
};
const user: User = {
name: 'Yamada',
};
const email = user.contact?.email; // OK:undefined(エラーなし)
console.log(email); // 出力: undefined
contact
が存在しない場合でも、user.contact?.email
のように?.
を使えば、エラーにはなりません。この書き方では、contact
がなければundefined
が返ってくるだけなので、安全にアクセスできます。このように、オプショナルチェーン(?.
)を使うと、安心してプロパティを参照することができます。
注意点
オプショナルチェーン(?.
)は、短絡評価(ショートサーキット)されます。つまり、左側(?.
より前)がnull
またはundefined
の場合、右側は評価されません。
const user = undefined;
const email = user?.contact.email;
この例では、user
がundefined
なので、右側(contact.email
)は評価されません。そのため、エラーにはならず、結果としてemail
はundefined
になります。
あわせて読みたい
オプショナルチェーン( 続きを見る?.
)については下記の記事で詳しく説明しています。興味のある方は下記のリンクからぜひチェックをしてみてください。 オプショナルチェーン(はてなドット?.)とは?「使い方」などを解説!
Null合体演算子(??)
Null合体演算子(??
)は、左側がnull
またはundefined
のときに、右側のデフォルト値を返す演算子です。変数がnull
やundefined
のときに「デフォルト値」を使いたい場合に便利です。
Null合体演算子(??
)を使用した場合の動作をサンプルコードで確認してみましょう。
// 例1:inputName が null の場合
const inputName1 = null;
const name1 = inputName1 ?? 'Guest';
console.log(name1); // 出力: 'Guest'
// 例2:inputName に 'Yamada' が入っている場合
const inputName2 = 'Yamada';
const name2 = inputName2 ?? 'Guest';
console.log(name2); // 出力: 'Yamada'
inputName1
がnull
なので、??
の右側"Guest"
が使われます。一方、inputName2
は"Yamada"
が代入されているので、"Yamada"
がそのまま使われます。
注意点
Null合体演算子(??
)は、OR演算子(||
)と似ていますが、対象としているものが違います。
??
はnull
とundefined
のみを対象にします||
はfalsy(falseっぽい値)全体を対象にします
具体的な違いをサンプルコードで見てみましょう。
// OR演算子(||)の場合
const age = 0 || 18;
console.log(age); // 出力: 18 (0 は falsy のため、右側が使われる)
上の例では、0
はfalsyなので、??
の右側の18
を返します。
// Null合体演算子(??)の場合
const age = 0 ?? 18;
console.log(age); // 出力: 0 (null / undefined ではないため、左側が使われる)
上の例では、0
はfalsyですが、null
やundefined
ではないので、??
の左側の0
を返します。
Null合体代入演算子(??=)
Null合体代入演算子(??=
)は、左辺の変数がnull
またはundefined
のときだけ、右辺の値を代入する演算子です。
Null合体代入演算子(??=
)を使用した場合のサンプルコードで動作を確認してみましょう。
// 左辺の変数が null の場合
const user = {
name: null,
};
user.name ??= 'Guest';
console.log(user.name); // 出力: 'Guest'
この例では、user.name
がnull
なので、右辺の"Guest"
が代入されます。
// 左辺の変数が null でも undefined でもない場合
const user = {
name: 'Alice',
};
user.name ??= 'Guest';
console.log(user.name); // 出力: 'Alice'
user.name
に"Alice"
が入っているため、右辺の"Guest"
は代入されません。
注意点
Null合体代入演算子(??=
)は左辺の変数がnull
またはundefined
のときだけ、右辺の値を代入するため、他のfalsy値(0
, ''
, false
など)では代入されません。
const user = { age: 0 };
user.age ??= 14;
console.log(user.age); // 出力: 0
上の例では、age
の値は0
でfalsyですが、null
やundefined
ではないので、14
は代入されません。
「Null合体代入演算子(??=)」と「Null合体演算子(??)」の違い
Null合体代入演算子(??=
)は、Null合体演算子(??
)を使った代入とほぼ同じ意味になります。以下のコードを見てみましょう。
const user = {
name: null,
};
// Null合体代入演算子(??=)を使った書き方
user.name ??= 'Guest';
// Null合体演算子(??)を使った書き方(上記と同じ意味)
user.name = user.name ?? 'Guest';
どちらも、「user.name
がnull
またはundefined
のときだけ、"Guest"
を代入する」という意味になります。
「!」に関する機能と使い方
!
には、以下のような機能があります。それぞれの用途や使い方について、順番にわかりやすく解説していきます。
- 非nullアサーション演算子(
!
)- 値がnullやundefinedではないことを開発者が主張する記号
- 明確な割り当てアサーション(
!
)- 変数が後で必ず初期化されることをコンパイラに伝える記号
非nullアサーション演算子(!)
非nullアサーション演算子(!
)は、「この値は絶対にnull
でもundefined
でもない」と開発者がコンパイラにアサート(主張)するための記号です。null
やundefined
を含む型(たとえばT | null
やT | null | undefined
)に対して、非nullアサーション演算子(!
)を使うと、型はT
であることをコンパイラに明示できます。
TypeScriptでは、変数や引数がnull
やundefined
を含む可能性がある場合、自動的に型安全のためのエラーを出してくれます。ですが、「このタイミングでは絶対にnull
やundefined
ではない」とわかっているときに非nullアサーション演算子(!
)を使うことで、エラーを回避できます。
非nullアサーション演算子(!
)を使用しない場合と使用した場合のサンプルコードで動作を確認してみましょう。
非nullアサーション演算子(!)を使用しない場合
type User = {
username: string;
};
function showUsername(user?: User) {
console.log(user.username); // エラー:'user' is possibly 'undefined'.
}
showUsername({ username: 'Taro' });
この例では、関数の引数user
が省略される可能性がある(User | undefined
)ので、TypeScriptは「user
はundefined
かもしれない」とエラーを出します。
非nullアサーション演算子(!)を使用した場合
type User = {
username: string;
};
function showUsername(user?: User) {
console.log(user!.username); // OK:「絶対に user はある」と明示している
}
showUsername({ username: 'Taro' }); // 出力: Taro
この例のようにuser!
と書くことで、「この変数は絶対にnull
やundefined
ではない」と主張できます。その結果、TypeScriptはエラーを出さずに通してくれます。
注意点
非nullアサーション演算子(!
)の使用には注意が必要です。
- 実行時に
null
やundefined
が入っていると、クラッシュの原因になります。 - コンパイラの型チェックを強制的に無効にしてしまうため、型の変化に気づけなくなる可能性があります。
const text: string | null = null;
console.log(text!.length); // 実行時エラー:Cannot read properties of null (reading 'length')
この例ではtext
に実際にはnull
が入っているので、.length
を読み込もうとしたときにエラーが発生してクラッシュします。
あわせて読みたい
非nullアサーション演算子( 続きを見る!
)については下記の記事で詳しく説明しています。興味のある方は下記のリンクからぜひチェックをしてみてください。 【TypeScript】非nullアサーション演算子(!)とは?使い方や注意点を解説!
明確な割り当てアサーション(!)
明確な割り当てアサーション(!
)は、「この変数は使う前に必ず初期化される」とコンパイラに伝えるための記号です。
TypeScriptでは、strictPropertyInitialization
が有効な場合(デフォルトでON)、変数やプロパティが確実に初期化されていないとエラーになります。ですが、どうしてもコンパイラに検出できないタイミングで代入される場合、開発者が明確な割り当てアサーション演算子(!
)をつけることで、「この変数は使う前に必ず初期化される」と明示できます。
明確な割り当てアサーション演算子(!
)を使用しない場合と使用した場合のサンプルコードで動作を確認してみましょう。
明確な割り当てアサーション(!)を使用しない場合
let title: string;
function setTitle(value: string) {
title = value;
}
setTitle('Hello TypeScript');
console.log(title); // エラー:Variable 'title' is used before being assigned.
この例では、title
は関数の中で値が代入されるため、見た目には初期化されているように見えます。でもTypeScriptは、関数setTitle
が先に呼ばれていることを静的には判断できないためエラーを出してきます。
明確な割り当てアサーション(!)を使用した場合
let title!: string; // 明確な割り当てアサーションを使う
function setTitle(value: string) {
title = value;
}
setTitle('Hello TypeScript');
console.log(title); // 出力: Hello TypeScript
title!
と明確な割り当てアサーション演算子(!
)を付けることで、「この変数title
は使う前に必ず初期化される」とTypeScriptに伝えることができます。その結果、TypeScriptはエラーを出さずに通してくれます。
注意点
初期化漏れに注意してください。
let title!: string; // 初期化されると信じ込ませているが...
// title = 'Hello TypeScript'; ← 初期化し忘れ!
console.log(title.length);
// 実行時エラー:Cannot read properties of undefined (reading 'length')
上記のコードでは、title
に!
をつけて「この変数title
は使う前に必ず初期化される」とTypeScriptに伝えていますが、実際には何も代入していないため、実行時にエラーが発生します。
本記事のまとめ
この記事ではTypeScriptでよく見かける?
や!
といった記号について解説しました。
各記号の違いをまとめると以下のようになります。
記号 | 機能名 | 主な用途 | JavaScript で使える? | TypeScript 対応バージョン |
---|---|---|---|---|
? | オプションパラメーター | 関数の引数を省略可能にする | ❌(TS限定) | ✅ TypeScript 1.1〜 |
?. | オプショナルチェーン | 安全にプロパティアクセス | ✅ ES2020〜 対応 | ✅ TypeScript 3.7〜 |
?? | Null合体演算子 | デフォルト値を使う | ✅ ES2020〜 対応 | ✅ TypeScript 3.7〜 |
??= | Null合体代入演算子 | null やundefined のときだけ代入 | ✅ ES2021〜 対応 | ✅ TypeScript 4.0〜 |
! | 非nullアサーション演算子 | null チェックを強制スキップ | ❌(TS限定) | ✅ TypeScript 2.0〜 |
! | 明確な割り当てアサーション | 後で初期化されると明示する | ❌(TS限定) | ✅ TypeScript 2.7〜 |
お読み頂きありがとうございました。