any が伝播することを説明でき、使わずに済ませる方法を持つunknown を安全な受け口として使えるas と ! が何も検査していないことを理解し、使う場所を限定できるany を付けた値には、どんな操作をしてもエラーになりません。
const x: any = "文字列";
x.toFixed(2); // エラーにならない(実行時に落ちる)
x.foo.bar.baz; // エラーにならない(実行時に落ちる)
x(); // エラーにならない(実行時に落ちる)
問題は、any が伝染することです。
function parse(json: string): any {
return JSON.parse(json);
}
const user = parse('{"name":"Alice"}'); // user は any
const name = user.name; // name も any
const upper = name.toUpperCase(); // upper も any
const len = upper.lenght; // 打ち間違いも通る(実行時 undefined)
1か所の any が、その先すべての検査を無効化します。
上の例では lenght という打ち間違いが検出されません。any を返す関数を1つ書くと、それを使うコード全体が型のない世界になります。
any は下流すべてに広がり、unknown は絞り込みを強制するstrict 型を書き忘れた引数は、暗黙に any になります。strict が有効ならエラーで教えてくれます。
function greet(name) {
// ~~~~
// error TS7006: Parameter 'name' implicitly has an 'any' type.
//
// 訳: 引数 'name' は暗黙的に 'any' 型になっています。
return "Hello, " + name;
}
03. tsconfig のとおり、TypeScript 7 では strict が既定で有効です。5 系から移行すると、このエラーが大量に出ることがあります。
unknown は any と同じくどんな値でも代入できます。違うのは使うときに絞り込みを強制される点です。
const x: unknown = "文字列";
x.toUpperCase();
// ~
// error TS18046: 'x' is of type 'unknown'.
//
// 訳: 'x' は 'unknown' 型です。
if (typeof x === "string") {
x.toUpperCase(); // ここでは string として扱える
}
| 項目 | any | unknown |
|---|---|---|
| どんな値でも代入できる | できる | できる |
| 確かめずに使える | 使える(危険) | 使えない(安全) |
| 他の型に代入できる | できる(危険) | できない |
| 伝播する | する | しない |
JSON.parse の戻り値は any です。受け口で unknown に変えると、検証を強制できます。
// unknown-parse.ts
function parseJson(text: string): unknown {
return JSON.parse(text);
}
type User = { name: string; age: number };
function isUser(v: unknown): v is User {
return (
typeof v === "object" && v !== null &&
typeof (v as Record<string, unknown>).name === "string" &&
typeof (v as Record<string, unknown>).age === "number"
);
}
const data = parseJson('{"name":"Alice","age":30}');
if (isUser(data)) {
console.log(`${data.name} は ${data.age} 歳`);
} else {
console.log("User の形ではありません");
}
console.log(isUser(parseJson('{"name":"Bob"}')));
$ npx tsx unknown-parse.ts
Alice は 30 歳
false
v is User という書き方は型ガード関数です。詳細は 08. 型の絞り込み で扱います。
実務ではライブラリを使ってください。
上のような検証を手で書くと、プロパティが増えるたびに書き足すことになります。zod・valibot のようなスキーマ検証ライブラリを使うと、検証と型定義を1か所で書けます。この資料では仕組みを理解するために手書きしています。実務での書き方は A3. 実務パターン集 に置いています。
as は型アサーションです。「この値はこの型だと思って扱え」とコンパイラに指示するだけで、値は何も変わりません。
const x: unknown = "文字列";
const n = x as number; // 通る。何も起きない
console.log(n.toFixed(2)); // 実行時エラー: n.toFixed is not a function
Java・C# から来た人へ
Java の (Integer) obj や C# の (int)obj は実行時に検査され、失敗すれば例外になります。TypeScript の as は実行時に何もしません。型が消えている以上、検査するものがないためです(01. 背景)。
C# の as 演算子(失敗時に null)とも違います。TypeScript の as は、検査を黙らせるだけです。
type User = { name: string; age: number };
const data = JSON.parse('{"name":"Alice"}') as User;
console.log(data.age.toFixed(0));
// 型の上では number なので通る
// 実行時: TypeError: Cannot read properties of undefined (reading 'toFixed')
外部から来た値に as を使うのが最も危険です。実際のデータが型と違っていても、誰も教えてくれません。
as は型の世界だけを書き換える。値は何も変わらない| 判定 | 場面 | 理由 |
|---|---|---|
| よい | 検証を済ませた直後(ブランド型の生成関数など) | 人間が保証した直後で、範囲が狭い |
| よい | as const | これは別物。リテラル型として固定する指示(04) |
| 条件付き | DOM の要素取得(as HTMLInputElement) | 型定義側が絞れないだけ。実物と一致するか確認する |
| 避ける | 外部から来たデータ(JSON・API・環境変数) | 検証していない。unknown と型ガードを使う |
| 避ける | エラーを消すため | エラーの原因が残ったままになる |
const s = "文字列";
const n = s as unknown as number; // 無関係な型にも変換できてしまう
as は近い型どうしでしか使えませんが、as unknown as を挟むと何にでも変換できます。この書き方が出てきたら、設計を見直す合図です。
as の代わりに使える演算子です。「この型を満たしているか確かめるが、推論された型はそのまま残す」という意味です。
type Config = Record<string, string | number>;
// as だと、推論された具体的な型が失われる
const a = { host: "localhost", port: 3000 } as Config;
// a.port の型は string | number
// satisfies なら、検査もされるし具体的な型も残る
const b = { host: "localhost", port: 3000 } satisfies Config;
// b.port の型は number
設定オブジェクトを書くときは satisfies が適しています。誤った値を検出しつつ、使う側では具体的な型を保てます。
function find(id: string): User | undefined { /* ... */ }
const user = find("1");
console.log(user.name);
// ~~~~
// error TS18048: 'user' is possibly 'undefined'.
console.log(user!.name); // ! を付けると通る(検査されない)
! は as の null 版です。実行時には何も起きません。間違っていれば TypeError になります。
| 判定 | 例 | 代わりに使うもの |
|---|---|---|
| 避ける | 検索結果が見つかる前提のとき | if (!user) throw new Error(...) で明示的に落とす |
| 避ける | 環境変数(process.env.PORT!) | 読み込み時に検証する(A3) |
| 条件付き | 直前に存在を確認済みだが、型が追従しないとき | できれば絞り込みで解決する(08) |
| よい | クラスのプロパティ定義(name!: string) | 初期化が別の場所で確実に行われる場合のみ |
// assert.ts
function findUser(id: string): { name: string } | undefined {
const db: Record<string, { name: string }> = { "1": { name: "Alice" } };
return db[id];
}
// ! を使わず、見つからないことを明示的に扱う
function getUserOrThrow(id: string): { name: string } {
const user = findUser(id);
if (!user) {
throw new Error(`ユーザーが見つかりません: ${id}`);
}
return user; // ここでは undefined でないことが型でも分かる
}
console.log(getUserOrThrow("1").name);
try {
getUserOrThrow("999");
} catch (e) {
console.log((e as Error).message);
}
$ npx tsx assert.ts
Alice
ユーザーが見つかりません: 999
どちらも実行時に落ちますが、落ち方が違います。
! を使うと TypeError: Cannot read properties of undefined という原因の分からないエラーになります。明示的に投げれば「何が見つからなかったか」が分かります。デバッグにかかる時間が変わります。
まとめ
| 道具 | 性質 | 使いどころ |
|---|---|---|
any | 検査しない。伝播する | ほぼ不要。使うなら消す前提でコメントを残す |
unknown | 代入はできるが、使う前に絞り込みが要る | 外部から来る値の受け口 |
as | 検査を黙らせる。値は変わらない | 検証済みの直後だけ。外部データには使わない |
satisfies | 検査するが、推論された型は残す | 設定オブジェクトの定義 |
! | null 版の as。実行時に何もしない | 避ける。明示的に例外を投げるほうが調べやすい |
次は 07. 関数の型 です。引数・戻り値・オーバーロード・コールバックの型を扱います。