【TypeScript】TS2531・TS2532の原因と解決方法|Object is possibly null/undefinedを完全解説

TypeScriptで開発していると、「Object is possibly ‘null’」「Object is possibly ‘undefined’」というエラーに頻繁に遭遇します。これらはそれぞれTS2531TS2532というエラーコードで、TypeScriptのstrictNullChecksが有効なプロジェクトでは避けて通れないエラーです。

このエラーは「nullundefinedの可能性がある値を、そのまま使おうとしている」ことを警告しています。実行時に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 化
  • 実務で使えるベストプラクティスとユーティリティ関数
スポンサーリンク
  1. TS2531・TS2532エラーとは?
    1. TS2531: Object is possibly ‘null’
    2. TS2532: Object is possibly ‘undefined’
    3. TS2531 と TS2532 の違いと共通点
    4. strictNullChecks との関係
    5. なぜ null/undefined チェックが重要なのか
  2. 基本的なケースと解決方法
    1. null の可能性がある変数へのアクセス(TS2531)
    2. undefined の可能性がある変数へのアクセス(TS2532)
    3. string | null 型のメソッド呼び出し(TS2531)
    4. T | undefined 型のプロパティアクセス(TS2532)
    5. 5つの解決パターン概要
  3. if / typeof による null チェック
    1. if (value !== null) パターン
    2. if (value !== undefined) パターン
    3. if (value != null) で null と undefined を同時チェック
    4. typeof による型ガード
    5. truthy チェック(if (value))の注意点
    6. 早期リターン(Early Return)パターン
    7. throw による型の絞り込み
  4. Optional Chaining(?.)での解決
    1. プロパティアクセス(obj?.prop)
    2. メソッド呼び出し(obj?.method())
    3. 配列要素アクセス(arr?.[0])
    4. ネストしたアクセス(a?.b?.c?.d)
    5. Optional Chaining の戻り値の型
    6. Optional Chaining だけでは解決しないケース
  5. Nullish Coalescing(??)での解決
    1. デフォルト値の設定(value ?? defaultValue)
    2. || との違い(0, “”, false の扱い)
    3. ??= 代入演算子
    4. ?. と ?? の組み合わせパターン
  6. Non-null Assertion(!)の使い方と注意点
    1. value! の構文と意味
    2. 使ってよいケース
    3. 使ってはいけないケース(ランタイムエラーの危険)
    4. ESLint の no-non-null-assertion ルール
    5. ! と as の違い
  7. DOM操作でのTS2531/TS2532
    1. document.getElementById() → HTMLElement | null
    2. document.querySelector() → Element | null
    3. element.parentElement → HTMLElement | null
    4. element.closest() → Element | null
    5. element.getAttribute() → string | null
    6. 正しいDOM要素の型ガードパターン
  8. 配列操作でのTS2531/TS2532
    1. Array.find() → T | undefined
    2. Array.pop() / Array.shift() → T | undefined
    3. arr[index] と noUncheckedIndexedAccess
    4. Map.get() → V | undefined
  9. React でのTS2531/TS2532
    1. useRef の初期値 null
    2. useState の nullable state
    3. useContext のデフォルト値
    4. optional props のアクセス
    5. event.currentTarget の型
  10. 関数の戻り値・引数でのTS2531/TS2532
    1. nullable な戻り値を使う側の対処
    2. オプショナル引数(param?: T)
    3. デフォルト引数での回避
    4. 型ガード関数(isNotNull / isDefined)の自作
  11. tsconfig.json の設定
    1. strictNullChecks の ON/OFF
    2. strict モードとの関係
    3. noUncheckedIndexedAccess
    4. exactOptionalPropertyTypes
    5. 段階的な strict 化の戦略
  12. 実務パターン集・ベストプラクティス
    1. Early Return パターン
    2. Guard Clause パターン
    3. Null Object パターン
    4. ユーティリティ関数(requireNonNull)
    5. 解決方法の選び方フローチャート
  13. まとめ
    1. 解決方法の早見表
    2. 覚えておくべきポイント
    3. 関連エラーの比較
    4. 関連記事

TS2531・TS2532エラーとは?

TS2531とTS2532は、TypeScriptコンパイラが出す型チェックエラーです。nullundefinedの可能性がある値に対して、プロパティアクセスやメソッド呼び出しを行おうとしたときに発生します。

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なので、valuenullの可能性があります。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.jsonstrictNullChecksが有効な場合にのみ発生します。この設定が無効だと、すべての型にnullundefinedが暗黙的に含まれるため、エラーは出ません。

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 チェックが重要なのか

nullundefinedの値に対してプロパティアクセスやメソッド呼び出しを行うと、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 を同時チェック

!=(緩い不等値)を使うと、nullundefinedの両方を同時にチェックできます。

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""falseNaN も 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で導入された演算子で、nullundefinedの可能性がある値に安全にアクセスする方法です。?.の左側が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])

配列がnullundefinedの可能性がある場合のパターンです。

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値すべてで右辺を返しますが、??nullundefinedのときだけ右辺を返します。

|| と ?? の違い
// || は 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

注意:数値や真偽値のデフォルト設定では || ではなく ?? を使いましょう。|| では 0false が意図せずデフォルト値に置き換わるバグが発生します。

左辺の値 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! の構文と意味

値の後ろに!を付けると、その値からnullundefinedが除外されます。

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 | undefinedT AB(互換性がある場合)
危険度 中(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のデフォルト値をnullundefinedにした場合、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 | nullT | 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)の自作

配列のフィルタリングなどで、nullundefinedを除外するための型ガード関数を自作できます。

型ガード関数の自作
// 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 は、nullundefinedを独立した型として扱うかどうかを制御します。

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 化のステップ

  1. Step 1strictNullChecks: true を有効にし、エラー数を確認
  2. Step 2// @ts-ignore で一時的にエラーを抑制(コメントで理由を記載)
  3. Step 3:モジュール単位で @ts-ignore を削除し、正しい null チェックに置き換え
  4. Step 4:すべての @ts-ignore を解消
  5. Step 5noUncheckedIndexedAccess: 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 は、関数が正常に返った場合に valueT 型であることを 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の型システムやエラー解決について、さらに詳しく学びたい方は以下の記事もご覧ください。