TypeScriptで開発していると、「Object is possibly ‘null’」「Object is possibly ‘undefined’」というエラーに頻繁に遭遇します。これらはそれぞれTS2531とTS2532というエラーコードで、TypeScriptのstrictNullChecksが有効なプロジェクトでは避けて通れないエラーです。
このエラーは「nullやundefinedの可能性がある値を、そのまま使おうとしている」ことを警告しています。実行時にnull参照エラー(TypeError: Cannot read properties of null)が発生するのをコンパイル時に防ぐための重要なチェックです。
この記事では、TS2531・TS2532エラーが発生するすべてのパターンを網羅的に解説し、if チェック・Optional Chaining(?.)・Nullish Coalescing(??)・Non-null Assertion(!)・型ガードなど、それぞれの正しい解決方法を具体的なコード付きで紹介します。DOM操作、配列操作、React、関数の引数・戻り値、tsconfig設定、実務パターンまで完全にカバーします。
この記事で学べること
- TS2531(Object is possibly ‘null’)・TS2532(Object is possibly ‘undefined’)の根本原因
- strictNullChecksとの関係と、なぜ null/undefined チェックが重要なのか
- if / typeof による null チェックパターン
- Optional Chaining(?.)とNullish Coalescing(??)による解決
- Non-null Assertion(!)の正しい使い方と危険なケース
- DOM操作(getElementById・querySelector)での型安全な実装
- 配列操作(find・pop・Map.get)での対処法
- React(useRef・useState・useContext)での解決パターン
- 関数の引数・戻り値での null/undefined 制御
- tsconfig.json の設定と段階的 strict 化
- 実務で使えるベストプラクティスとユーティリティ関数
TS2531・TS2532エラーとは?
TS2531とTS2532は、TypeScriptコンパイラが出す型チェックエラーです。nullやundefinedの可能性がある値に対して、プロパティアクセスやメソッド呼び出しを行おうとしたときに発生します。
TS2531: Object is possibly ‘null’
TS2531は、値がnullの可能性がある場合に発生するエラーです。
error TS2531: Object is possibly ‘null’.
TS2531 エラーの例
function getName(value: string | null) {
// Error: TS2531: Object is possibly 'null'.
return value.toUpperCase();
}
この例では、valueの型がstring | nullなので、valueがnullの可能性があります。nullに対して.toUpperCase()を呼ぶと実行時にエラーになるため、TypeScriptがコンパイル時に警告しています。
TS2532: Object is possibly ‘undefined’
TS2532は、値がundefinedの可能性がある場合に発生するエラーです。
error TS2532: Object is possibly ‘undefined’.
TS2532 エラーの例
function getLength(value?: string) {
// Error: TS2532: Object is possibly 'undefined'.
return value.length;
}
この例では、valueがオプショナル引数(?付き)なので、型はstring | undefinedです。undefinedに対して.lengthにアクセスすると実行時エラーになるため、TypeScriptがコンパイル時に警告しています。
TS2531 と TS2532 の違いと共通点
この2つのエラーは本質的に同じ問題を扱っています。違いは、nullが原因かundefinedが原因かだけです。
| 比較項目 |
TS2531 |
TS2532 |
| エラーメッセージ |
Object is possibly 'null' |
Object is possibly 'undefined' |
| 原因 |
値が null の可能性 |
値が undefined の可能性 |
| 典型的なケース |
DOM API、明示的な null 代入 |
オプショナル引数、Array.find()、Map.get() |
| 解決方法 |
if チェック、Optional Chaining(?.)、Nullish Coalescing(??)、Non-null Assertion(!)、型ガード |
| 設定依存 |
strictNullChecks: true(または strict: true)のとき発生 |
null と undefined の違い
- null:「値が存在しないことを明示的に表す」。プログラマが意図的に「空」を設定したケース(例:
document.getElementById() が要素を見つけられなかった場合)
- undefined:「値が定義されていないことを表す」。変数が初期化されていない、オプショナル引数が渡されなかった、オブジェクトに存在しないプロパティにアクセスしたケース
strictNullChecks との関係
TS2531・TS2532エラーは、tsconfig.jsonのstrictNullChecksが有効な場合にのみ発生します。この設定が無効だと、すべての型にnullとundefinedが暗黙的に含まれるため、エラーは出ません。
strictNullChecks の ON/OFF による違い
// strictNullChecks: false の場合
// → string 型に null/undefined が含まれるため、エラーなし
let value: string = null; // OK(危険!)
console.log(value.length); // コンパイルOK → 実行時 TypeError!
// strictNullChecks: true の場合
// → string 型に null/undefined は含まれない
let value: string = null; // Error: TS2322
let value2: string | null = null; // OK
console.log(value2.length); // Error: TS2531
注意:strictNullChecksを無効にしてエラーを「消す」のは解決策ではありません。実行時にTypeErrorが発生するバグを隠すだけです。strictNullChecks は常に有効にし、適切な null チェックを行うのがベストプラクティスです。
なぜ null/undefined チェックが重要なのか
nullやundefinedの値に対してプロパティアクセスやメソッド呼び出しを行うと、JavaScriptランタイムでTypeErrorが発生します。これはアプリケーションのクラッシュにつながる重大なバグです。
null/undefined アクセス時の実行時エラー
const value = null;
value.toString();
// TypeError: Cannot read properties of null (reading 'toString')
const obj = undefined;
obj.name;
// TypeError: Cannot read properties of undefined (reading 'name')
TypeScriptのTS2531・TS2532は、この種のバグをコードを実行する前に検出してくれます。エラーを「邪魔なもの」と考えるのではなく、「バグを未然に防いでくれるガードレール」として積極的に活用しましょう。
基本的なケースと解決方法
TS2531・TS2532が発生する最も基本的なケースと、5つの解決パターンの概要を見ていきましょう。
null の可能性がある変数へのアクセス(TS2531)
明示的にnullを含むユニオン型の変数に対して、プロパティやメソッドにアクセスするとTS2531が発生します。
TS2531: null の可能性がある変数
let username: string | null = null;
// 何らかの処理で値が入るかもしれない
username = getUserNameFromDB();
// Error: TS2531: Object is possibly 'null'.
console.log(username.toUpperCase());
console.log(username.length);
console.log(username[0]);
undefined の可能性がある変数へのアクセス(TS2532)
オプショナルな値や、undefinedを含むユニオン型に対して同様の操作を行うとTS2532が発生します。
TS2532: undefined の可能性がある変数
interface Config {
apiUrl?: string;
timeout?: number;
}
const config: Config = {};
// Error: TS2532: Object is possibly 'undefined'.
console.log(config.apiUrl.toUpperCase());
console.log(config.timeout.toFixed(2));
string | null 型のメソッド呼び出し(TS2531)
関数の戻り値がstring | nullの場合、戻り値に対して直接メソッドを呼ぶとTS2531が発生します。
string | null 型のメソッド呼び出し
function findUser(id: number): string | null {
const users = ['Alice', 'Bob', 'Charlie'];
return users[id] ?? null;
}
const user = findUser(5);
// Error: TS2531: Object is possibly 'null'.
console.log(user.toUpperCase());
console.log(user.length);
console.log(user.trim());
T | undefined 型のプロパティアクセス(TS2532)
オブジェクト全体がundefinedの可能性がある場合、そのプロパティにアクセスするとTS2532が発生します。
T | undefined 型のプロパティアクセス
interface User {
name: string;
email: string;
}
function getUser(id: number): User | undefined {
const users: User[] = [{ name: 'Alice', email: 'alice@example.com' }];
return users.find(u => u.name === 'Bob');
}
const user = getUser(1);
// Error: TS2532: Object is possibly 'undefined'.
console.log(user.name);
console.log(user.email);
5つの解決パターン概要
TS2531・TS2532を解決する方法は、大きく5つあります。それぞれの特徴と使い分けを理解することが重要です。
| 解決方法 |
構文 |
安全性 |
用途 |
| if チェック |
if (value !== null) |
安全 |
null/undefined の場合の処理が必要なとき |
| Optional Chaining |
value?.prop |
安全 |
null/undefined なら undefined を返せばよいとき |
| Nullish Coalescing |
value ?? default |
安全 |
null/undefined のときデフォルト値を使うとき |
| Non-null Assertion |
value! |
危険 |
確実に null/undefined でないと分かるとき |
| 型ガード関数 |
function isX(v): v is X |
安全 |
複雑な条件で型を絞り込むとき |
次のセクションから、それぞれの解決方法を詳しく解説していきます。
if / typeof による null チェック
if文によるnullチェックは、TS2531・TS2532を解決する最も基本的で安全な方法です。TypeScriptはif文内の条件を「型の絞り込み(Type Narrowing)」として認識し、ブロック内で型を自動的に絞り込みます。
if (value !== null) パターン
nullとの厳密な不等値チェックで、nullの可能性を除外できます。
if (value !== null) で TS2531 を解決
function greet(name: string | null) {
// Error: TS2531: Object is possibly ''null''.
// console.log(name.toUpperCase());
// 解決: null チェックを追加
if (name !== null) {
// この中では name は string 型に絞り込まれる
console.log(name.toUpperCase()); // OK
} else {
console.log(''名前が設定されていません'');
}
}
if (value !== undefined) パターン
undefinedとの厳密な不等値チェックで、undefinedの可能性を除外できます。
if (value !== undefined) で TS2532 を解決
function processConfig(timeout?: number) {
// Error: TS2532: Object is possibly ''undefined''.
// console.log(timeout.toFixed(2));
if (timeout !== undefined) {
console.log(timeout.toFixed(2)); // OK
}
}
if (value != null) で null と undefined を同時チェック
!=(緩い不等値)を使うと、nullとundefinedの両方を同時にチェックできます。
if (value != null) で両方チェック
function processValue(value: string | null | undefined) {
if (value != null) {
console.log(value.length); // OK: string
}
}
ポイント:!= null は null/undefined 同時チェックの推奨パターンです。ESLint で "eqeqeq": ["error", "always", { "null": "ignore" }] とすると null チェックだけ許可できます。
typeof による型ガード
typeof演算子による型チェックもTypeScriptの型絞り込みとして認識されます。
typeof による型ガード
function formatValue(value: string | number | null | undefined) {
if (typeof value === ''string'') {
return value.toUpperCase(); // OK: string
}
if (typeof value === ''number'') {
return value.toFixed(2); // OK: number
}
return ''値なし'';
}
注意:typeof null は ''object'' を返します(JavaScript の歴史的仕様バグ)。null チェックには value !== null を使いましょう。
truthy チェック(if (value))の注意点
if (value) でも null/undefined を除外できますが、0・""・false・NaN も falsy として除外されます。
truthy チェックの注意点
// string | null なら truthy チェックOK
function processName(name: string | null) {
if (name) {
console.log(name.toUpperCase()); // OK
}
}
// number | null では注意!
function processCount(count: number | null) {
// NG: count=0 → false
if (count) { /* 0はスキップ */ }
// OK: 厳密チェック
if (count !== null) {
console.log(count.toFixed(2)); // 0.00 も処理
}
}
| チェック方法 |
除外される値 |
推奨場面 |
!== null |
null のみ |
null だけ除外 |
!== undefined |
undefined のみ |
undefined だけ除外 |
!= null |
null + undefined |
両方除外(推奨) |
if (value) |
falsy値すべて |
string の null チェック |
早期リターン(Early Return)パターン
null/undefined の場合に先に return することでネストを避ける実務定番パターンです。
早期リターンで型を絞り込む
function processUser(user: User | null) {
if (user === null) return;
// ここ以降 user は User 型
if (!user.email) return;
sendEmail(user.email);
}
throw による型の絞り込み
throw も TypeScript の型絞り込みとして認識されます。
throw で型を絞り込む
function getRequiredUser(id: number): User {
const user = findUserById(id); // User | null
if (user === null) {
throw new Error(''User not found'');
}
return user; // OK: User 型
}
Optional Chaining(?.)での解決
Optional Chaining(オプショナルチェーン)は、ES2020で導入された演算子で、nullやundefinedの可能性がある値に安全にアクセスする方法です。?.の左側がnullまたはundefinedの場合、式全体がundefinedを返し、実行時エラーを回避します。
プロパティアクセス(obj?.prop)
オブジェクトのプロパティにアクセスする場合の基本パターンです。
Optional Chaining でプロパティアクセス
interface User {
name: string;
address?: {
city: string;
zip: string;
};
}
const user: User = { name: 'Alice' };
// Error: TS2532: Object is possibly 'undefined'.
// console.log(user.address.city);
// 解決: Optional Chaining
console.log(user.address?.city); // undefined
console.log(user.address?.zip); // undefined
メソッド呼び出し(obj?.method())
メソッドの呼び出しにもOptional Chainingが使えます。
Optional Chaining でメソッド呼び出し
interface Logger {
log(msg: string): void;
error(msg: string): void;
}
let logger: Logger | null = null;
// Error: TS2531: Object is possibly 'null'.
// logger.log('hello');
// 解決
logger?.log('hello'); // null なので何も起きない
logger?.error('oops'); // null なので何も起きない
配列要素アクセス(arr?.[0])
配列がnullやundefinedの可能性がある場合のパターンです。
Optional Chaining で配列要素アクセス
function getFirstItem(items: string[] | null) {
// Error: TS2531: Object is possibly 'null'.
// return items[0];
return items?.[0]; // string | undefined
}
// 関数呼び出しにも使える
type Callback = ((v: string) => void) | undefined;
function execute(cb: Callback) {
cb?.('hello'); // undefined なら何も起きない
}
ネストしたアクセス(a?.b?.c?.d)
深くネストしたオブジェクトでは、Optional Chainingを連鎖させることができます。
ネストした Optional Chaining
interface Company {
name: string;
ceo?: {
name: string;
contact?: {
email?: string;
phone?: string;
};
};
}
function getCeoEmail(company: Company): string | undefined {
// Optional Chaining なら1行
return company.ceo?.contact?.email;
}
Optional Chaining の戻り値の型
Optional Chainingの結果には常にundefinedが含まれます。これは重要なポイントです。
Optional Chaining の戻り値の型
interface Config {
database?: {
host: string;
port: number;
};
}
const config: Config = {};
const host = config.database?.host;
// host の型: string | undefined
// string 型の変数に直接代入するとエラー
// const requiredHost: string = config.database?.host; // TS2322
// ?? と組み合わせてデフォルト値を設定
const requiredHost: string = config.database?.host ?? 'localhost';
Optional Chaining だけでは解決しないケース
Optional Chainingは万能ではありません。確実に非null値が必要な場面では if文が必要です。
Optional Chaining では解決できないケース
// ケース1: 確実に string が必要な関数引数
function requireString(value: string) { /* ... */ }
const name: string | null = getName();
// requireString(name?.toString()); // TS2345: string | undefined
// → if文で確認する
if (name !== null) {
requireString(name); // OK
}
// ケース2: 代入の左辺には使えない
const obj: { value: number } | null = getObj();
// obj?.value = 10; // SyntaxError
if (obj !== null) {
obj.value = 10; // OK
}
// ケース3: 分割代入には使えない
const user: User | null = getUser();
if (user !== null) {
const { name, email } = user; // OK
}
Optional Chaining の使い分けまとめ
- 使ってよい場面:null/undefined のとき undefined を返せばよいケース
- 使えない場面:確実に非null値が必要なケース(関数の引数、代入の左辺、分割代入)
- ベストプラクティス:
?. の結果は ?? でデフォルト値を設定するか、if文で絞り込む
Nullish Coalescing(??)での解決
Nullish Coalescing(ヌリッシュコアレッシング)演算子??は、左辺がnullまたはundefinedのときだけ右辺の値を返す演算子です。||と似ていますが、0や""(空文字列)を有効な値として扱える点が大きな違いです。
デフォルト値の設定(value ?? defaultValue)
null/undefined の場合にデフォルト値を設定する、最も基本的な使い方です。
?? でデフォルト値を設定
function getDisplayName(name: string | null): string {
// name が null なら 'ゲスト' を返す
return name ?? 'ゲスト';
}
getDisplayName('Alice'); // 'Alice'
getDisplayName(null); // 'ゲスト'
function getTimeout(config?: { timeout?: number }): number {
return config?.timeout ?? 3000;
}
getTimeout({ timeout: 5000 }); // 5000
getTimeout({ timeout: 0 }); // 0(0 は有効な値)
getTimeout({}); // 3000
getTimeout(); // 3000
|| との違い(0, “”, false の扱い)
||はfalsy値すべてで右辺を返しますが、??はnullとundefinedのときだけ右辺を返します。
|| と ?? の違い
// || は falsy 値すべてで右辺を返す
0 || 10; // 10(0 は falsy)
'' || 'N/A'; // 'N/A'('' は falsy)
false || true; // true(false は falsy)
null || 10; // 10
// ?? は null/undefined のときだけ右辺を返す
0 ?? 10; // 0(0 は有効な値)
'' ?? 'N/A'; // ''(空文字は有効な値)
false ?? true; // false(false は有効な値)
null ?? 10; // 10
注意:数値や真偽値のデフォルト設定では || ではなく ?? を使いましょう。|| では 0 や false が意図せずデフォルト値に置き換わるバグが発生します。
| 左辺の値 |
value || default |
value ?? default |
null |
default |
default |
undefined |
default |
default |
0 |
default |
0 |
"" |
default |
“” |
false |
default |
false |
NaN |
default |
NaN |
??= 代入演算子
??=は、変数がnullまたはundefinedのときだけ右辺の値を代入する代入演算子です。
??= 代入演算子
let name: string | null = null;
name ??= 'デフォルト';
// name === 'デフォルト'
let count: number | undefined;
count ??= 0;
// count === 0
// 既に値がある場合は代入されない
let value: number | null = 42;
value ??= 0;
// value === 42(変更されない)
?. と ?? の組み合わせパターン
Optional Chaining(?.)と Nullish Coalescing(??)を組み合わせることで、ネストしたオブジェクトに安全にアクセスしつつデフォルト値を設定できます。これは実務で非常によく使われるパターンです。
?. と ?? の組み合わせ
interface AppConfig {
server?: {
host?: string;
port?: number;
};
debug?: boolean;
}
function initApp(config?: AppConfig) {
const host = config?.server?.host ?? 'localhost';
const port = config?.server?.port ?? 3000;
const debug = config?.debug ?? false;
console.log(`Server: ${host}:${port}, Debug: ${debug}`);
}
initApp();
// Server: localhost:3000, Debug: false
initApp({ server: { host: 'api.example.com', port: 8080 }, debug: true });
// Server: api.example.com:8080, Debug: true
ポイント:?. + ?? の組み合わせは、設定オブジェクトのデフォルト値設定で定番パターンです。TS2531/TS2532 の解決と同時に、型安全なデフォルト値設定ができます。
Non-null Assertion(!)の使い方と注意点
Non-null Assertion(非nullアサーション)演算子!は、TypeScriptに「この値はnullでもundefinedでもない」と明示的に伝える構文です。コンパイラのチェックを無視するため、使い方を間違えるとランタイムエラーにつながる危険な機能です。
value! の構文と意味
値の後ろに!を付けると、その値からnullとundefinedが除外されます。
Non-null Assertion の基本
function processName(name: string | null) {
// Error: TS2531: Object is possibly 'null'.
// console.log(name.toUpperCase());
// Non-null Assertion で解決(! を付ける)
console.log(name!.toUpperCase()); // OK(コンパイルは通る)
}
// name! の型は string(null が除外される)
const value: string | null = 'hello';
const definite: string = value!; // OK
使ってよいケース
Non-null Assertionは、プログラマがロジック上確実にnull/undefinedでないと分かっているが、TypeScriptが推論できない場合に限り使用します。
Non-null Assertion を使ってよいケース
// ケース1: 直前に null チェック済みだが TS が認識できない
const map = new Map<string, number>();
map.set('key', 42);
if (map.has('key')) {
// has() で存在確認済みだが、get() の戻り値は number | undefined
const value = map.get('key')!; // OK: 確実に存在する
}
// ケース2: テストコード
const element = document.getElementById('root')!;
// テスト環境で要素の存在が保証されている場合
// ケース3: 初期化が別の場所で行われるクラスプロパティ
class App {
// definite assignment assertion
db!: Database; // init() で初期化される
async init() {
this.db = await connectDatabase();
}
}
使ってはいけないケース(ランタイムエラーの危険)
Non-null Assertionは型チェックを無視するだけで、実行時の動作には一切影響しません。値が実際にnull/undefinedの場合、ランタイムエラーが発生します。
Non-null Assertion の危険な使い方
// NG: 実際に null の可能性がある
const el = document.getElementById('maybe-not-exist')!;
el.textContent = 'Hello';
// → 要素がなければ TypeError: Cannot set properties of null
// NG: API レスポンスに ! を使う
const response = await fetch('/api/user');
const data = await response.json();
console.log(data.user!.name); // user が null なら TypeError
// NG: find() の結果に ! を使う
const users = [{ id: 1, name: 'Alice' }];
const bob = users.find(u => u.name === 'Bob')!;
console.log(bob.id); // Bob がいなければ TypeError
注意:Non-null Assertion (!) は「TypeScriptを黙らせる」ための道具ではありません。! を多用するコードは、strictNullChecks を無効にしているのと同じくらい危険です。まず if チェックや ?? での解決を検討し、! は最後の手段にしましょう。
ESLint の no-non-null-assertion ルール
TypeScript ESLintには@typescript-eslint/no-non-null-assertionルールがあり、!の使用を禁止または警告できます。
.eslintrc.json の設定例
{
"rules": {
// Non-null Assertion を警告
"@typescript-eslint/no-non-null-assertion": "warn",
// より厳しく: エラーにする
// "@typescript-eslint/no-non-null-assertion": "error"
}
}
! と as の違い
!(Non-null Assertion)とas(型アサーション)はどちらも型チェックを上書きする機能ですが、目的が異なります。
| 比較項目 |
value! |
value as T |
| 目的 |
null/undefined の除外 |
任意の型への変換 |
| 効果 |
T | null | undefined → T |
A → B(互換性がある場合) |
| 危険度 |
中(null/undefinedの除外のみ) |
高(任意の型に変換可能) |
| 推奨度 |
限定的に使用可 |
極力避ける |
! と as の違い
const value: string | null = getString();
// ! は null/undefined だけを除外
const a: string = value!; // string | null → string
// as は型を変換(互換性チェックあり)
const b: string = value as string; // string | null → string
// この場合は ! の方が意図が明確
DOM操作でのTS2531/TS2532
DOM APIのメソッドの多くはnullを返す可能性があるため、TS2531が頻繁に発生します。要素が見つからなかった場合にnullを返すのはDOMの仕様です。
document.getElementById() → HTMLElement | null
getElementById()は指定IDの要素が見つからない場合nullを返します。
getElementById の型安全な使い方
// getElementById の戻り値は HTMLElement | null
const el = document.getElementById('app');
// Error: TS2531: Object is possibly 'null'.
// el.textContent = 'Hello';
// パターン1: if チェック
if (el) {
el.textContent = 'Hello'; // OK
}
// パターン2: 早期リターン
const root = document.getElementById('root');
if (!root) {
throw new Error('#root element not found');
}
root.textContent = 'Hello'; // OK: HTMLElement 型
// パターン3: Non-null Assertion(要素の存在が確実な場合のみ)
const app = document.getElementById('app')!;
app.textContent = 'Hello'; // コンパイルOK(要素がないとランタイムエラー)
document.querySelector() → Element | null
querySelector()もセレクタに一致する要素がない場合nullを返します。さらに、戻り値の型がElementであるため、特定のHTML要素として扱うには型アサーションが必要です。
querySelector の型安全な使い方
// querySelector はジェネリクスで型を指定できる
const input = document.querySelector<HTMLInputElement>('input[name=email]');
// input の型: HTMLInputElement | null
if (input) {
console.log(input.value); // OK: HTMLInputElement の value にアクセス
}
// 複数要素の場合は querySelectorAll(null を返さない)
const buttons = document.querySelectorAll<HTMLButtonElement>('button');
// buttons の型: NodeListOf<HTMLButtonElement>(null ではない)
buttons.forEach(btn => {
btn.disabled = true; // OK
});
element.parentElement → HTMLElement | null
DOMツリーのナビゲーションプロパティの多くもnullを返す可能性があります。
DOM ナビゲーションプロパティの null チェック
const child = document.getElementById('child');
if (child) {
// parentElement も null の可能性あり
const parent = child.parentElement;
// Error: TS2531 - parentElement は HTMLElement | null
// parent.classList.add('active');
// Optional Chaining で安全にアクセス
parent?.classList.add('active');
// nextElementSibling / previousElementSibling も同様
child.nextElementSibling?.classList.add('next');
}
element.closest() → Element | null
closest()は祖先要素を検索し、見つからなければnullを返します。
closest() の型安全な使い方
function handleClick(event: MouseEvent) {
const target = event.target as HTMLElement;
// closest() は Element | null
const card = target.closest<HTMLDivElement>('.card');
if (card) {
console.log(card.dataset.id); // OK
}
}
element.getAttribute() → string | null
getAttribute()は属性が存在しない場合nullを返します。
getAttribute の型安全な使い方
const link = document.querySelector('a');
if (link) {
const href = link.getAttribute('href');
// href: string | null
// ?? でデフォルト値を設定
const url = href ?? '#';
console.log(url.toUpperCase()); // OK: string
}
正しいDOM要素の型ガードパターン
DOM操作のTS2531を安全に解決するためのユーティリティ関数パターンです。
DOM要素の型ガードユーティリティ
// 要素が見つからない場合にエラーをスローするヘルパー
function getElement<T extends HTMLElement>(
selector: string
): T {
const el = document.querySelector<T>(selector);
if (!el) {
throw new Error(`Element not found: ${selector}`);
}
return el;
}
// 使い方
const form = getElement<HTMLFormElement>('#loginForm');
form.addEventListener('submit', handleSubmit); // OK
const input = getElement<HTMLInputElement>('#email');
console.log(input.value); // OK
| DOM API |
戻り値の型 |
null チェック必要 |
getElementById() |
HTMLElement | null |
必要 |
querySelector() |
Element | null |
必要 |
querySelectorAll() |
NodeListOf<Element> |
不要(空リスト) |
closest() |
Element | null |
必要 |
getAttribute() |
string | null |
必要 |
parentElement |
HTMLElement | null |
必要 |
nextElementSibling |
Element | null |
必要 |
配列操作でのTS2531/TS2532
配列のメソッドやコレクション型には、undefinedを返す可能性があるものが多く、TS2532が頻繁に発生します。
Array.find() → T | undefined
find()は条件に一致する要素が見つからない場合undefinedを返します。これはTS2532の最も一般的な原因の1つです。
Array.find() の TS2532 対処
interface Product {
id: number;
name: string;
price: number;
}
const products: Product[] = [
{ id: 1, name: 'りんご', price: 100 },
{ id: 2, name: 'バナナ', price: 200 },
];
const found = products.find(p => p.id === 3);
// found: Product | undefined
// Error: TS2532: Object is possibly 'undefined'.
// console.log(found.name);
// パターン1: if チェック
if (found) {
console.log(found.name); // OK
console.log(found.price); // OK
}
// パターン2: Optional Chaining
console.log(found?.name); // string | undefined
// パターン3: ?? でデフォルト値
const name = found?.name ?? '商品なし';
Array.pop() / Array.shift() → T | undefined
pop()とshift()は配列が空のときundefinedを返します。
pop() / shift() の TS2532 対処
const stack: number[] = [1, 2, 3];
const last = stack.pop();
// last: number | undefined
// Error: TS2532
// console.log(last.toFixed(2));
if (last !== undefined) {
console.log(last.toFixed(2)); // OK: '3.00'
}
// shift() も同様
const queue: string[] = ['task1', 'task2'];
const first = queue.shift();
// first: string | undefined
const task = first ?? 'no task';
arr[index] と noUncheckedIndexedAccess
通常、配列のインデックスアクセスはundefinedを含みません。しかし、noUncheckedIndexedAccessを有効にすると、インデックスアクセスの結果にundefinedが追加されます。
noUncheckedIndexedAccess の効果
const arr = [1, 2, 3];
// noUncheckedIndexedAccess: false(デフォルト)
const val = arr[0]; // number(undefined を含まない)
val.toFixed(2); // OK
// noUncheckedIndexedAccess: true
const val2 = arr[0]; // number | undefined
// val2.toFixed(2); // Error: TS2532
// 解決: チェックを追加
if (val2 !== undefined) {
val2.toFixed(2); // OK
}
Map.get() → V | undefined
Map.get()はキーが存在しない場合undefinedを返します。
Map.get() の TS2532 対処
const cache = new Map<string, number>();
cache.set('price', 100);
const price = cache.get('price');
// price: number | undefined
// Error: TS2532
// console.log(price.toFixed(2));
// パターン1: has() で事前チェック + !
if (cache.has('price')) {
const p = cache.get('price')!;
console.log(p.toFixed(2)); // OK
}
// パターン2: ?? でデフォルト値
const safePrice = cache.get('price') ?? 0;
console.log(safePrice.toFixed(2)); // OK
// パターン3: undefined チェック
const val = cache.get('price');
if (val !== undefined) {
console.log(val.toFixed(2)); // OK
}
| メソッド |
戻り値の型 |
undefined になるケース |
Array.find() |
T | undefined |
条件に一致する要素がない |
Array.pop() |
T | undefined |
配列が空 |
Array.shift() |
T | undefined |
配列が空 |
arr[index] |
T / T | undefined |
noUncheckedIndexedAccess有効時 |
Map.get() |
V | undefined |
キーが存在しない |
WeakMap.get() |
V | undefined |
キーが存在しない |
React でのTS2531/TS2532
Reactの開発では、useRefの初期値、useStateのnullable state、useContextのデフォルト値など、TS2531/TS2532が頻繁に発生します。
useRef の初期値 null
useRefでDOM要素を参照する場合、初期値はnullになります。そのため、ref.currentにアクセスするときにTS2531が発生します。
useRef の null チェック
import { useRef, useEffect } from 'react';
function VideoPlayer() {
const videoRef = useRef<HTMLVideoElement>(null);
// videoRef.current: HTMLVideoElement | null
useEffect(() => {
// Error: TS2531: Object is possibly 'null'.
// videoRef.current.play();
// 解決: null チェック
if (videoRef.current) {
videoRef.current.play();
}
}, []);
return <video ref={videoRef} src='movie.mp4' />;
}
useState の nullable state
APIからデータを取得する場合など、stateの初期値がnullのケースがよくあります。
useState の nullable state
interface User {
id: number;
name: string;
email: string;
}
function UserProfile() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser().then(data => {
setUser(data);
setLoading(false);
});
}, []);
if (loading) return <p>読み込み中...</p>;
// Error: TS2531: Object is possibly 'null'.
// return <p>{user.name}</p>;
// 解決: null チェック
if (!user) return <p>ユーザーが見つかりません</p>;
// ここ以降 user は User 型に絞り込まれる
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
useContext のデフォルト値
createContextのデフォルト値をnullやundefinedにした場合、Context を使用する側でTS2531/TS2532が発生します。
useContext の型安全なパターン
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
// パターン1: デフォルト値を null にする
const AuthContext = createContext<AuthContextType | null>(null);
// カスタムフック(null チェック付き)
function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (context === null) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
// 使用側: null チェック不要
function Dashboard() {
const { user, logout } = useAuth(); // AuthContextType
// ...
}
optional props のアクセス
Reactコンポーネントのpropsがオプショナルな場合、TS2532が発生します。
optional props の対処
interface ButtonProps {
label: string;
onClick?: () => void;
icon?: string;
className?: string;
}
function Button({ label, onClick, icon, className }: ButtonProps) {
// onClick は optional → onClick?: () => void | undefined
// Error: TS2532 if called directly
// onClick();
return (
<button
className={className ?? 'btn-default'}
onClick={onClick} {/* JSX では undefined を渡してもOK */}
>
{icon && <span>{icon}</span>}
{label}
</button>
);
}
event.currentTarget の型
ReactイベントのcurrentTargetは型安全にアクセスできます。
React イベントの型
function Form() {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// currentTarget は HTMLInputElement(null ではない)
console.log(e.currentTarget.value); // OK
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget); // OK
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
</form>
);
}
関数の戻り値・引数でのTS2531/TS2532
関数がnullable な値を返す場合や、オプショナル引数を受け取る場合にTS2531/TS2532が発生します。呼び出し側での適切な対処が必要です。
nullable な戻り値を使う側の対処
関数がT | nullやT | undefinedを返す場合、呼び出し側でチェックが必要です。
nullable 戻り値の対処パターン
function findUserByEmail(email: string): User | null {
const users: User[] = getUsers();
return users.find(u => u.email === email) ?? null;
}
const user = findUserByEmail('alice@example.com');
// Error: TS2531
// console.log(user.name);
// パターン1: if + early return
if (!user) {
console.log('User not found');
return;
}
console.log(user.name); // OK
// パターン2: Optional Chaining + ??
const name = user?.name ?? 'Unknown';
オプショナル引数(param?: T)
関数のオプショナル引数はT | undefined型になります。
オプショナル引数の対処
function greet(name?: string, greeting?: string) {
// name: string | undefined
// greeting: string | undefined
// ?? でデフォルト値を設定
const displayName = name ?? 'ゲスト';
const displayGreeting = greeting ?? 'こんにちは';
console.log(`${displayGreeting}、${displayName}さん`);
}
デフォルト引数での回避
デフォルト引数を使えば、そもそもTS2532を発生させないようにできます。これが最もシンプルな解決策です。
デフォルト引数で TS2532 を回避
// デフォルト引数を使えば undefined チェック不要
function greet(name = 'ゲスト', greeting = 'こんにちは') {
// name: string(undefined ではない)
// greeting: string(undefined ではない)
console.log(`${greeting}、${name}さん`);
console.log(name.toUpperCase()); // OK: TS2532 なし
}
greet(); // 'こんにちは、ゲストさん'
greet('Alice'); // 'こんにちは、Aliceさん'
greet('Alice', 'Hello'); // 'Hello、Aliceさん'
ポイント:デフォルト引数は、Optional引数(?)+ ?? よりも簡潔です。nullではなく undefined だけを扱う場合は、デフォルト引数が最適です。
型ガード関数(isNotNull / isDefined)の自作
配列のフィルタリングなどで、nullやundefinedを除外するための型ガード関数を自作できます。
型ガード関数の自作
// null を除外する型ガード
function isNotNull<T>(value: T | null): value is T {
return value !== null;
}
// undefined を除外する型ガード
function isDefined<T>(value: T | undefined): value is T {
return value !== undefined;
}
// null と undefined を両方除外する型ガード
function isNonNullable<T>(value: T): value is NonNullable<T> {
return value != null;
}
// 使い方: 配列から null/undefined をフィルタリング
const mixed: (string | null | undefined)[] = [
'Alice', null, 'Bob', undefined, 'Charlie'
];
// filter だけでは型が絞り込まれない
const bad = mixed.filter(v => v != null);
// bad: (string | null | undefined)[]
// 型ガード関数を使えば型が絞り込まれる
const good = mixed.filter(isNonNullable);
// good: string[]
good.forEach(name => {
console.log(name.toUpperCase()); // OK: string
});
ポイント:isNonNullable 型ガード関数は、配列の .filter() と組み合わせるのが最も効果的です。filter(Boolean) よりも型推論が正確になります。
tsconfig.json の設定
TS2531/TS2532 エラーは tsconfig.json の設定と密接に関係しています。ここでは、null/undefined チェックに関連する設定オプションを解説します。
strictNullChecks の ON/OFF
strictNullChecks は、nullとundefinedを独立した型として扱うかどうかを制御します。
tsconfig.json
{
"compilerOptions": {
// strictNullChecks を個別に有効化
"strictNullChecks": true,
// または strict で一括有効化(推奨)
"strict": true
}
}
| 設定 |
strictNullChecks |
TS2531/TS2532 |
安全性 |
false |
無効 |
発生しない |
低(ランタイムエラーの危険) |
true |
有効 |
発生する |
高(コンパイル時に検出) |
strict モードとの関係
strict: true を設定すると、strictNullChecksを含む以下のすべてのstrictオプションが有効になります。
strict: true で有効になるオプション
// "strict": true は以下すべてを有効にするのと同じ
{
"compilerOptions": {
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true,
"useUnknownInCatchVariables": true
}
}
noUncheckedIndexedAccess
noUncheckedIndexedAccessを有効にすると、配列のインデックスアクセスやオブジェクトのブラケットアクセスの結果にundefinedが追加されます。
noUncheckedIndexedAccess の効果
// noUncheckedIndexedAccess: true
const arr = [1, 2, 3];
const first = arr[0]; // number | undefined
const obj: Record<string, number> = { a: 1 };
const val = obj['b']; // number | undefined
// for-of ループでは undefined にならない
for (const item of arr) {
console.log(item.toFixed(2)); // OK: number
}
noUncheckedIndexedAccess の推奨
strict: true には含まれないため、別途有効にする必要あり
- 配列の範囲外アクセスによるバグを防げる
- 既存プロジェクトに導入すると大量のエラーが出る可能性あり
- 新規プロジェクトでは有効にすることを推奨
exactOptionalPropertyTypes
exactOptionalPropertyTypesを有効にすると、オプショナルプロパティにundefinedを明示的に代入することが禁止されます。
exactOptionalPropertyTypes の効果
interface Config {
color?: string; // '色が指定されていない' と 'undefined' を区別
}
// exactOptionalPropertyTypes: false(デフォルト)
const a: Config = { color: undefined }; // OK
// exactOptionalPropertyTypes: true
const b: Config = { color: undefined }; // Error!
const c: Config = {}; // OK(プロパティ自体が存在しない)
段階的な strict 化の戦略
既存プロジェクトにstrictNullChecksを導入する場合、一度にすべてのエラーを修正するのは現実的ではありません。段階的に導入しましょう。
段階的 strict 化のステップ
- Step 1:
strictNullChecks: true を有効にし、エラー数を確認
- Step 2:
// @ts-ignore で一時的にエラーを抑制(コメントで理由を記載)
- Step 3:モジュール単位で
@ts-ignore を削除し、正しい null チェックに置き換え
- Step 4:すべての
@ts-ignore を解消
- Step 5:
noUncheckedIndexedAccess: true を追加
実務パターン集・ベストプラクティス
TS2531/TS2532を適切に解決するための、実務で役立つパターンとベストプラクティスを紹介します。
Early Return パターン
関数の先頭でnull/undefinedチェックを行い、早期にreturnすることで、後続のコードを型安全にするパターンです。
Early Return パターン
function processOrder(order: Order | null) {
if (!order) return;
// ここ以降 order は Order 型
const items = order.items;
if (items.length === 0) return;
const total = items.reduce((sum, item) => sum + item.price, 0);
processPayment(total);
}
Guard Clause パターン
条件を満たさない場合にエラーをスローすることで、後続のコードの型を保証するパターンです。
Guard Clause パターン
// 汎用の assert 関数
function assertNonNull<T>(
value: T | null | undefined,
message?: string
): asserts value is T {
if (value == null) {
throw new Error(message ?? 'Expected non-null value');
}
}
// 使い方
function getUser(id: number): User {
const user = users.find(u => u.id === id);
assertNonNull(user, `User ${id} not found`);
// ここ以降 user は User 型
return user;
}
asserts キーワードとは
asserts value is T は、関数が正常に返った場合に value が T 型であることを TypeScript に伝える
- 関数が throw した場合は、以降のコードが実行されないため型が保証される
- Non-null Assertion (
!) と違い、実行時にもチェックされるため安全
Null Object パターン
null の代わりにデフォルトオブジェクトを使うパターンです。null チェックが不要になります。
Null Object パターン
interface Logger {
log(message: string): void;
error(message: string): void;
}
// Null Object: 何もしないロガー
const nullLogger: Logger = {
log() {},
error() {},
};
// Logger | null ではなく、常に Logger を使う
function createApp(logger: Logger = nullLogger) {
// null チェック不要
logger.log('App started'); // OK
}
ユーティリティ関数(requireNonNull)
null/undefinedの場合にエラーをスローし、non-null値を返すユーティリティ関数です。
requireNonNull ユーティリティ
// non-null を要求するユーティリティ
function requireNonNull<T>(
value: T | null | undefined,
message?: string
): T {
if (value == null) {
throw new Error(message ?? 'Required value is null or undefined');
}
return value;
}
// 使い方
const el = requireNonNull(
document.getElementById('root'),
'#root element is required'
);
// el: HTMLElement(null ではない)
const user = requireNonNull(
users.find(u => u.id === targetId),
`User ${targetId} not found`
);
// user: User(undefined ではない)
解決方法の選び方フローチャート
TS2531/TS2532が発生したとき、どの解決方法を使うべきかの判断基準です。
| 状況 |
推奨解決方法 |
理由 |
| null の場合の処理が必要 |
if (value !== null) |
else ブロックで代替処理を書ける |
| プロパティを読むだけ |
value?.prop |
簡潔、undefined を返す |
| デフォルト値がある |
value ?? default |
0 や “” を有効値として扱える |
| null なら処理を中断 |
Early Return |
ネストを避けて読みやすい |
| null なら重大なバグ |
throw / assertNonNull |
バグを即座に検出 |
| 配列の null 除外 |
.filter(isNonNullable) |
型安全にフィルタリング |
| 確実に non-null と分かる |
value!(最終手段) |
他の方法が使えない場合のみ |
まとめ
TS2531(Object is possibly 'null')とTS2532(Object is possibly 'undefined')は、TypeScriptがnull/undefined参照によるランタイムエラーをコンパイル時に防ぐための重要なチェックです。
解決方法の早見表
| 解決方法 |
構文例 |
安全性 |
使いどころ |
| if チェック |
if (v !== null) |
安全 |
最も基本的、else の処理が必要なとき |
| != null |
if (v != null) |
安全 |
null と undefined を同時に除外 |
| Optional Chaining |
v?.prop |
安全 |
プロパティアクセス、メソッド呼び出し |
| Nullish Coalescing |
v ?? default |
安全 |
デフォルト値の設定 |
| ?. + ?? |
v?.prop ?? default |
安全 |
ネストしたアクセス + デフォルト値 |
| Early Return |
if (!v) return; |
安全 |
関数の先頭でガード |
| throw |
if (!v) throw ... |
安全 |
null が許容されないケース |
| assertion 関数 |
assertNonNull(v) |
安全 |
再利用可能なガード |
| 型ガード関数 |
.filter(isNonNullable) |
安全 |
配列の null/undefined 除外 |
| デフォルト引数 |
fn(x = default) |
安全 |
オプショナル引数の代替 |
| Non-null Assertion |
v! |
危険 |
最終手段、確実に non-null のときのみ |
覚えておくべきポイント
TS2531/TS2532 解決のポイント
- strictNullChecks は常に有効にする(バグの早期発見に不可欠)
- ! の多用は避ける(ランタイムエラーの原因になる)
- ?. と ?? を組み合わせるのが実務では最も使いやすい
- Early Return / throw で型を絞り込むとコードが読みやすくなる
- 型ガード関数を自作すると、配列のフィルタリングで型が正確になる
- DOM API は常に null を返す可能性があるので、null チェックを忘れない
- React では useRef の初期値、useState の nullable state に注意
関連エラーの比較
| エラーコード |
メッセージ |
意味 |
| TS2531 |
Object is possibly 'null' |
null の可能性があるオブジェクトへのアクセス |
| TS2532 |
Object is possibly 'undefined' |
undefined の可能性があるオブジェクトへのアクセス |
| TS2322 |
Type 'X' is not assignable to type 'Y' |
型の不一致(null/undefined の代入含む) |
| TS2339 |
Property 'X' does not exist on type 'Y' |
存在しないプロパティへのアクセス |
| TS2345 |
Argument of type 'X' is not assignable to parameter of type 'Y' |
関数引数の型不一致 |
関連記事
TypeScriptの型システムやエラー解決について、さらに詳しく学びたい方は以下の記事もご覧ください。