0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScriptのunknown型を正しく理解する ― anyとの違いと使いどころ

Posted at

はじめに

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の多用は避けるべき

ここからが重要です。

unknownanyより安全ですが、本当に型がわからない場合の最終手段であり、
安易に使うべきではありません。

理由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: バリデーションライブラリで型とチェックを一致させられる

zodio-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...ではなく型定義を頑張る

unknownanyの上位互換ではなく、最終手段です。

まずはジェネリクスやバリデーションライブラリで解決できないか検討し、それでも難しい場合にのみunknownを使いましょう。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?