TypeScript/JavaScript における null と undefined 完全ガイド
1. 基礎理解編:null と undefined の違い
🎯 なぜこれを理解する必要があるのか?
TypeScript/JavaScript では「値がない」を表現する方法が 2 つあります。この違いを理解しないと:
- 予期しないバグが発生する
- チーム開発で混乱が生じる
- 型安全性が損なわれる
📊 基本的な違い一覧表
| 項目 | undefined | null |
|---|---|---|
| 意味 | 値が代入されていない | 値の意図的な不在 |
| 発生方法 | 自然発生 | 明示的代入のみ |
| 型 | 変数(上書き可能) | リテラル(上書き不可) |
| typeof 結果 | "undefined" |
"object" |
| JSON 化 | プロパティ削除 | nullとして保持 |
💡 具体例で理解する
// 1. 自然発生 vs 明示的代入
let a; // undefined(自然発生)
let b = null; // null(明示的代入)
// 2. オブジェクトプロパティ
const obj = {};
console.log(obj.name); // undefined(存在しないプロパティ)
// 3. 関数の戻り値
function greet() {}
console.log(greet()); // undefined(return文なし)
// 4. typeof演算子の違い
typeof undefined; // "undefined"
typeof null; // "object" ← 歴史的な仕様バグ
// 5. JSON化の違い
JSON.stringify({
name: "John",
age: undefined,
avatar: null,
});
// 結果: '{"name":"John","avatar":null}'
// ageは削除される
⚠️ よくある誤解
// ❌ 間違った理解
if (value == null) {
// これは undefined と null の両方をチェックする
}
// ✅ 正しい理解
if (value === null) { // nullのみ
if (value === undefined) { // undefinedのみ
if (value == null) { // 両方(推奨される場合もある)
2. 歴史・背景編:なぜ両方存在するのか
🕰️ JavaScript 誕生の背景(1995 年)
設計者の制約
- Brendan Eich(JavaScript 作成者)が 10 日間で言語を設計
- 上司から「JavaScript を Java のように見せろ」という命令
- 当時は例外処理機能がなかった
設計上の判断
// Javaから借用した概念
null; // 「オブジェクトではない」値(Java由来)
// JavaScript独自の必要性
undefined; // 「原始値でもオブジェクトでもない」値
🎨 設計意図の詳細
1. Java との互換性
// Java風の書き方を可能にするため
String name = null; // Java
var name = null; // JavaScript(Java風)
2. 変数の状態表現
// 異なる「値がない」状態を区別
let user; // undefined: まだ初期化されていない
user = null; // null: 意図的に「値なし」を設定
🤔 現在の評価:設計ミス
JavaScript 作成者自身が認める問題:
- 2 つの「値なし」は混乱の元
- 後方互換性により削除不可能
- 現代では 1 つで十分だったと判明
🔄 なぜ削除されないのか
// 既存コードが大量に存在
if (document.getElementById("button") === null) {
// 削除すると全世界のWebサイトが壊れる
}
JavaScript の鉄則:後方互換性を絶対に破らない
3. 実践編:どちらを使うべきか
🎯 結論:undefined を推奨
📈 Microsoft TypeScript 公式ガイドライン
「null は使わない」 - たった 1 行のシンプルなルール
🚀 undefined 推奨の理由
1. 自然な言語動作
// undefinedは自然に発生する
let user; // undefined
const obj = {};
console.log(obj.name); // undefined
function getName() {}
getName(); // undefined
// nullにするには余計な変換が必要
const converted = user ?? null; // 無駄な作業
2. チーム開発での一貫性
// ❌ 判断に迷うパターン
function getUser(): User | null | undefined {
// どっちを返せばいい?混乱の元
}
// ✅ シンプルなパターン
function getUser(): User | undefined {
// 迷わない
}
3. TypeScript との相性
// Optional Properties(推奨)
interface User {
name: string;
email?: string; // string | undefined
}
// 冗長なnull記述
interface UserBad {
name: string;
email: string | null | undefined; // 複雑
}
📝 実装パターン比較
// ✅ 推奨パターン
class UserService {
async getUser(id: string): Promise<User | undefined> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) return undefined;
return await response.json();
} catch {
return undefined;
}
}
}
// ❌ 非推奨パターン
class UserServiceBad {
async getUser(id: string): Promise<User | null> {
// nullを返す理由が不明確
return null;
}
}
4. 特殊ケース編:null が必要な場面
🎭 例外:React コンポーネントの条件付きレンダリング
React 特有の仕様
// ✅ 正しい:Reactコンポーネント非表示
function UserProfile({ user }: { user: User | undefined }) {
if (!user) return null; // Reactの「何もレンダリングしない」仕様
return <div>{user.name}</div>;
}
// ❌ 間違い:データ値でのnull使用
const [user, setUser] = useState<User | null>(null); // 避ける
データとレンダリングの区別
// データ値:undefined使用
const userData: User | undefined = getUser();
// React戻り値:null使用(仕様)
function Component() {
return userData ? <UserProfile user={userData} /> : null;
}
🌐 外部システムとの連携
1. データベースの NULL 値
// Supabaseから返されるnull(仕様上受け入れ)
const { data } = await supabase.from("users").select("avatar_url").single();
// 内部的にundefinedに正規化
const avatarUrl = data.avatar_url ?? undefined;
2. 明示的な値削除
// APIで「このフィールドを削除して」を表現
const updateData = {
name: "新しい名前",
description: null, // 明示的削除
// avatar: undefined だとJSONから除外される
};
// PATCH /api/users/123
// {"name":"新しい名前","description":null}
3. JSON 通信での意図表現
// フィールドの存在/削除を区別したい場合
interface UserUpdate {
name?: string; // undefined = 更新しない
avatar?: string | null; // null = 削除する, string = 更新
}
5. エコシステム別対応編:環境による使い分け
🏗️ 環境別推奨パターン
フロントエンド(TypeScript/React)
// ✅ undefined統一
interface UserState {
data?: User;
loading: boolean;
error?: string;
}
const [user, setUser] = useState<User | undefined>();
Node.js(レガシーパターン)
// Error-first callback(歴史的理由でnull使用)
fs.readFile("/path", (err, data) => {
if (err) return callback(err);
callback(null, data); // null = エラーなし
});
// モダンなPromise/async-await
async function readFile(path) {
try {
const data = await fs.promises.readFile(path);
return data;
} catch {
return undefined; // undefinedを推奨
}
}
データベース(SQL)
-- データベースではNULL標準
SELECT name FROM users WHERE avatar_url IS NULL;
UPDATE users SET description = NULL; -- 明示的削除
GraphQL
# GraphQL仕様ではnull
type User {
name: String!
avatar: String # nullableフィールド
}
🔄 境界での変換戦略
// 外部システムとの境界で正規化
class APIService {
// 外部APIから受信(nullを受け入れ)
private async fetchExternal(): Promise<User | null> {
// 外部API呼び出し
}
// 内部向けに正規化(undefinedに変換)
async getUser(id: string): Promise<User | undefined> {
const result = await this.fetchExternal();
return result ?? undefined;
}
}
6. 実装ガイド編:具体的なコーディング指針
📋 チェックリスト
✅ 推奨事項
- 新しいコードでは
undefinedを使用 - Optional Properties を活用(
property?:) - 外部 API からの
nullは内部でundefinedに正規化 - React コンポーネントでは条件的に
nullを返す
❌ 避けるべき事項
- 新規で
nullを使った型定義 -
undefinedとnullを混在させる - 両方を許可する複雑な型(
string | null | undefined)
🔧 実装テンプレート
1. API 応答の型定義
// ✅ 推奨
interface ApiResponse<T> {
data?: T;
error?: string;
loading: boolean;
}
// ❌ 非推奨
interface ApiBadResponse<T> {
data: T | null | undefined; // 複雑
}
2. 状態管理(React + jotai)
// ✅ 推奨
const userAtom = atom<User | undefined>(undefined);
// 使用例
function useUser() {
const [user, setUser] = useAtom(userAtom);
const fetchUser = async (id: string) => {
try {
const response = await fetch(`/api/users/${id}`);
const userData = response.ok ? await response.json() : undefined;
setUser(userData);
} catch {
setUser(undefined);
}
};
return { user, fetchUser };
}
3. Utility 関数
// null/undefined統一チェック
function isEmpty(value: unknown): value is null | undefined {
return value == null; // 両方をチェック
}
// undefined正規化
function normalize<T>(value: T | null): T | undefined {
return value ?? undefined;
}
// デフォルト値提供
function withDefault<T>(value: T | undefined, defaultValue: T): T {
return value ?? defaultValue;
}
4. 条件付きレンダリングパターン
// ✅ 推奨:コンポーネント内で判断
function UserProfile({ user }: { user: User | undefined }) {
if (!user) return null; // React仕様
return (
<div>
<h1>{user.name}</h1>
<img src={user.avatar ?? "/default-avatar.png"} />
</div>
);
}
// ✅ 親コンポーネントでの制御も可能
function App() {
const user = useUser();
return (
<div>
<Header />
{user && <UserProfile user={user} />}
<Footer />
</div>
);
}
🎯 段階的移行戦略
Phase 1: 新規コード
- 新しく書くコードは
undefined統一 - 外部ライブラリの
nullは受け入れつつ内部で正規化
Phase 2: 境界最適化
- API 境界で
null→undefined変換層を追加 - 型定義を徐々に
undefinedベースに更新
Phase 3: レガシー改善
- 既存コードのリファクタリング(破壊的変更に注意)
- チーム内でのコーディング規約統一
📚 さらなる学習リソース
- TypeScript Handbook - Null and Undefined
- React 公式ドキュメント - Conditional Rendering
- MDN - null vs undefined
🎉 まとめ
- 基本方針:
undefinedを推奨、nullは特定用途のみ - React: データは
undefined、コンポーネント戻り値はnull - 外部連携: 境界で正規化、内部は
undefined統一 - チーム開発: シンプルなルールで一貫性を保つ
最重要ポイント: 完璧を目指さず、新しいコードから段階的に改善していく姿勢が大切です。
おわり 😊