🦓
TypeScriptでよく見る型エラー
概要
この記事では、TypeScriptで発生する型エラーの一般的な原因と解決方法についてまとめてみました。
特に、ZodスキーマとTypeScript型定義の間の不整合によるエラーに焦点を当てます。
問題の例
エラーメッセージ
Type '{ id: undefined; propertyId: undefined; price: null; date: null; }' is not assignable to type '{ id?: number | undefined; propertyId?: number | undefined; price?: number | undefined; date?: string[] | null | undefined; }'.
Types of property 'price' are incompatible.
Type 'null' is not assignable to type 'number | undefined'.
問題の原因
このエラーは以下の要因によって発生します:
- スキーマと型定義の不整合
- null vs undefined の混在
- デフォルト値の型不一致
- 配列型と単一型の混同
一般的な解決アプローチ
1. 原因の特定
エラーメッセージの解析
- エラーメッセージは具体的なプロパティ名と期待される型を示します
- 例:
Types of property 'price' are incompatible
スキーマと型定義の確認
// 問題のある例
// スキーマ定義
const schema = z.object({
price: z.number().optional(), // number | undefined
date: z.string().nullable().optional() // string | null | undefined
})
// 型定義
type MyType = {
price?: number | null, // 不整合:null が含まれている
date?: string[] | null // 不整合:配列型になっている
}
2. スキーマと型の整合性確保
Zodスキーマの型生成
// 正しい例
const priceSchema = z.object({
price: z.number().nullable().optional(),
date: z.array(z.string()).nullable().optional()
})
// 自動生成される型
type PriceType = z.infer<typeof priceSchema>
// {
// price?: number | null | undefined
// date?: string[] | null | undefined
// }
手動型定義との整合性
// 手動で型を定義する場合
type PriceType = {
price?: number | null
date?: string[] | null
}
// スキーマと一致させる
const priceSchema = z.object({
price: z.number().nullable().optional(),
date: z.array(z.string()).nullable().optional()
})
3. null vs undefined の適切な使い分け
型の違い
// optional() - undefined を許可
z.number().optional() // number | undefined
// nullable() - null を許可
z.number().nullable() // number | null
// nullable().optional() - 両方を許可
z.number().nullable().optional() // number | null | undefined
使用場面の判断
// undefined: 値が設定されていない状態
const userSchema = z.object({
name: z.string().optional(), // 名前が未設定
email: z.string().optional() // メールが未設定
})
// null: 意図的に空の値
const priceSchema = z.object({
regularPrice: z.number().nullable(), // 価格が未定
discountPrice: z.number().nullable() // 割引なし
})
// 両方: 柔軟性が必要な場合
const configSchema = z.object({
theme: z.string().nullable().optional(), // テーマ設定なし/未設定
language: z.string().nullable().optional() // 言語設定なし/未設定
})
4. デフォルト値の型一致
問題のある例
const schema = z.object({
price: z.number().optional()
})
// エラー:null を number | undefined に代入しようとしている
const defaultValues = {
price: null // ❌ 型エラー
}
正しい例
// スキーマを修正
const schema = z.object({
price: z.number().nullable().optional()
})
const defaultValues = {
price: null // ✅ 型一致
}
5. 配列型の適切な定義
問題のある例
// スキーマ:単一の文字列
const schema = z.object({
dates: z.string().nullable().optional() // string | null | undefined
})
// 型定義:配列
type MyType = {
dates?: string[] | null // 不整合
}
// 使用箇所:配列として使用
const dates = watch('dates') as string[] // 型アサーションが必要
正しい例
// スキーマ:配列の文字列
const schema = z.object({
dates: z.array(z.string()).nullable().optional() // string[] | null | undefined
})
// 型定義:配列
type MyType = {
dates?: string[] | null // 整合
}
// 使用箇所:自然に配列として使用
const dates = watch('dates') || [] // 型アサーション不要
実装チェックリスト
1. スキーマ定義の確認
- 各フィールドの型が正しく定義されているか
-
optional()とnullable()の使い分けが適切か -
配列型が必要な場合は
z.array()を使用しているか
2. 型定義の整合性
- 手動型定義がある場合、スキーマと一致しているか
-
z.infer<typeof schema>を使用して自動生成しているか -
型の組み合わせ(
| null | undefined)が正しいか
3. デフォルト値の確認
- デフォルト値の型がスキーマと一致しているか
-
nullとundefinedの使い分けが適切か - 配列のデフォルト値が正しく設定されているか
4. 使用箇所の検証
- コンポーネントでの型アサーションが不要になっているか
-
配列メソッド(
.length,.map()など)が正常に動作するか - フォームライブラリとの連携が正常か
よくあるエラーパターン
パターン1: null vs undefined の混在
// エラー
const schema = z.object({ price: z.number().optional() })
const defaults = { price: null } // ❌
// 解決
const schema = z.object({ price: z.number().nullable().optional() })
const defaults = { price: null } // ✅
パターン2: 配列型の不整合
// エラー
const schema = z.object({ items: z.string().optional() })
type Type = { items?: string[] } // ❌
// 解決
const schema = z.object({ items: z.array(z.string()).optional() })
type Type = { items?: string[] } // ✅
パターン3: ネストしたオブジェクトの型不一致
// エラー
const schema = z.object({
config: z.object({
theme: z.string().optional()
}).optional()
})
const defaults = { config: { theme: null } } // ❌
// 解決
const schema = z.object({
config: z.object({
theme: z.string().nullable().optional()
}).optional()
})
const defaults = { config: { theme: null } } // ✅
ベストプラクティス
1. 単一の情報源
// 推奨:スキーマを単一の情報源として使用
const schema = z.object({...})
type MyType = z.infer<typeof schema>
// 避ける:手動で型を定義
type MyType = {...} // スキーマと同期が取れなくなる可能性
2. 一貫した命名規則
// 推奨:明確な命名
const userCreateSchema = z.object({...})
const userUpdateSchema = z.object({...})
const userResponseSchema = z.object({...})
// 避ける:曖昧な命名
const schema = z.object({...})
const userSchema = z.object({...})
3. 段階的な型定義
// 推奨:段階的に型を定義
const baseSchema = z.object({
id: z.number(),
name: z.string()
})
const createSchema = baseSchema.omit({ id: true })
const updateSchema = baseSchema.partial()
const responseSchema = baseSchema.extend({
createdAt: z.date()
})
まとめ
簡単ですが、
TypeScript型エラーの解決は、以下の手順より、型安全性を保ちながら、保守性の高いコードを実現できます。
- エラーメッセージの詳細な解析
- スキーマと型定義の整合性確認
- null vs undefined の適切な使い分け
- デフォルト値の型一致
- 実際の使用箇所での動作確認
誰かの参考になれば嬉しいです。
Discussion