TypeScript で「Property does not exist on type」が解消できない
Property 'x' does not exist on type 'Y' は実装より型定義が狭い時に出る。
interface 拡張・型ガードでの絞り込み・宣言マージなど、原因に応じて使い分ける。
#property#type#interface#narrowing#declare-global
公開:
要約
Property 'x' does not exist on type 'Y'. は 実装が知っているが型定義が知らないプロパティ にアクセスしたときに出る。
「型がそもそも狭い」「ユニオン型で絞り込んでいない」「宣言が無い」のどれかを切り分け、対応する書き方を選ぶ。
よくある原因
- 型を明示していない:
const user = await res.json()の戻り値がanyではなく狭い型に推論され、必要なプロパティが含まれていない - ユニオン型のまま参照:
type Shape = Circle | Squareでshape.radiusを読むと、Squareに無いため拒否される(Narrowing 公式) - 型定義ファイル不在:
import x from "old-lib"のold-libに.d.tsが無くanyかunknown型になる - グローバル拡張の不足:
window.MY_APP = ...のようにグローバル変数を追加したが、declare globalで型を足していない
解決策
1. 型を明示する
interface User {
id: string;
name: string;
email: string;
}
async function fetchUser(): Promise<User> {
const res = await fetch("/api/me");
return (await res.json()) as User;
}Object Types 公式ハンドブック のとおり、interface か type で必要なプロパティを宣言する。
受け取った値はまず unknown に置き、型ガードで絞り込むとより安全。
2. ユニオン型は絞り込んでから読む
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number };
function area(s: Shape) {
if (s.kind === "circle") {
return Math.PI * s.radius ** 2; // Circle に絞られる
}
return s.size * s.size;
}判別可能ユニオン(discriminated union)にしておくと switch や if で型が確定する。
詳細は 型の絞り込み を参照。
3. 型未提供パッケージに対応する
npm install --save-dev @types/lodash公式型定義が無いなら自前で書く。
プロジェクトに types/old-lib.d.ts を作成し、tsconfig.json の include に含める。
declare module "old-lib" {
export function legacyFn(input: string): number;
}4. global を拡張する
// src/types/global.d.ts
export {};
declare global {
interface Window {
MY_APP: { version: string };
}
}宣言マージ公式 のとおり、interface は同名で複数回書くと自動でマージされる。tsconfig.json の include に src/types/** が含まれているかも確認する。