はじめに
G's(旧G’s Academy)東京LAB18期卒のエンジニアをしております
H_Kagami_Gsと申します。
今回はG'sのG’s EXPANSION PROGRAMというアドオン講座で
TypeScriptの講座を受講しており、
課題でany型について調べたのですが、
最近AIで実装するとunknown型を目にする機会があったので、
こちらも理解を深めたく記事を作成しました。
TypeScriptで型が不明なデータを扱うとき、anyを使っていませんか?
unknown型は**型安全なany**として導入されましたが、
「どう使い分けるのか」「そもそも使うべきなのか」が曖昧なまま
使っている方も多いのではないでしょうか。
この記事では、unknown型の基本から実践的な使い方、
そして多用すべきでない理由まで解説します。
unknown型とは
unknownはTypeScript 3.0で導入された型であり、
何が入っているかわからない値を表現します。
anyと同様に任意の値を代入できますが、
型を確認するまで操作できないという制約があります。
anyとの違い
// any: 何でもできてしまう(危険)
let valueAny: any = "hello";
valueAny.toFixed(); // コンパイル通る → 実行時エラー💥
// unknown: 型を確認するまで操作できない(安全)
let valueUnknown: unknown = "hello";
valueUnknown.toFixed(); // コンパイルエラー!🛡️
比較表
| 特性 | any |
unknown |
|---|---|---|
| 任意の値を代入できる | ✅ | ✅ |
| 他の型に代入できる | ✅ | ❌ |
| プロパティアクセス | できる | 型ガード必須 |
| メソッド呼び出し | できる | 型ガード必須 |
| 型安全性 | なし | あり |
使い方:型ガードで絞り込む
unknown型の値を使うには、型ガードで型を絞り込む必要があります。
function process(value: unknown) {
// ❌ そのままでは何もできない
// console.log(value.length); // エラー
// ✅ typeofで絞り込む
if (typeof value === "string") {
console.log(value.toUpperCase()); // OK: string型として扱える
}
// ✅ instanceofで絞り込む
if (value instanceof Date) {
console.log(value.getTime()); // OK: Date型として扱える
}
// ✅ in演算子で絞り込む
if (value !== null && typeof value === "object" && "name" in value) {
console.log(value.name); // OK
}
}
⚠️ ただしunknownの多用は避けるべき
ここからが重要です。
unknownはanyより安全ですが、本当に型がわからない場合の最終手段であり、
安易に使うべきではありません。
理由1: ジェネリクスで解決できることが多い
// ❌ unknownを使いがち
function parse(json: string): unknown {
return JSON.parse(json);
}
const data = parse('{"id": 1}');
// data.id にアクセスするには型ガードが必要...
// ✅ ジェネリクスで呼び出し側が型を指定
function parse<T>(json: string): T {
return JSON.parse(json);
}
interface User {
id: number;
name: string;
}
const user = parse<User>('{"id": 1, "name": "田中"}');
console.log(user.id); // OK
理由2: バリデーションライブラリで型とチェックを一致させられる
zodやio-tsを使えば、実行時バリデーションと型定義を一元管理できます。
import { z } from "zod";
// スキーマを定義すると型も自動生成される
const UserSchema = z.object({
id: z.number(),
name: z.string(),
});
// スキーマから型を取得
type User = z.infer<typeof UserSchema>;
// 実行時に型安全にパース
function fetchUser(json: string): User | null {
const result = UserSchema.safeParse(JSON.parse(json));
return result.success ? result.data : null;
}
理由3: 型ガードがコードベースに散らばる
unknownを引き回すと、使う場所ごとに型チェックが必要になります。
// ❌ あちこちで型ガードが必要になる
function getUser(): unknown { /* ... */ }
function showUserName() {
const user = getUser();
if (user && typeof user === "object" && "name" in user) {
console.log(user.name);
}
}
function showUserId() {
const user = getUser();
if (user && typeof user === "object" && "id" in user) {
console.log(user.id); // また同じようなチェック...
}
}
これは可読性・保守性の低下につながります。
unknownを使うべき場面
では、いつunknownを使うべきでしょうか?
1. 本当に型が予測不能な場合
- プラグインシステムで外部コードを実行する
-
evalや動的スクリプトの結果を受け取る - 完全に未知のサードパーティAPIを扱う
2. ライブラリの境界で一時的に受け取る場合
// APIレスポンスを受け取った直後
async function fetchData(url: string): Promise<unknown> {
const response = await fetch(url);
return response.json();
}
// すぐにバリデーションして型を確定
async function getUser(id: string): Promise<User> {
const data = await fetchData(`/api/users/${id}`);
return UserSchema.parse(data); // ここで型が確定
}
鉄則
「unknownを書いたら、すぐ近くで型を確定させる」
unknownのまま引き回しているコードがあれば、それは設計の見直しサインです。
まとめ
| 状況 | 推奨アプローチ |
|---|---|
| 外部APIのレスポンス | zod等でバリデーション |
| 汎用的な関数 | ジェネリクスを使う |
| 型が本当に不明 |
unknown + すぐに型確定 |
| とりあえず動かしたい |
any...ではなく型定義を頑張る |
unknownはanyの上位互換ではなく、最終手段です。
まずはジェネリクスやバリデーションライブラリで解決できないか検討し、それでも難しい場合にのみunknownを使いましょう。