できない.dev

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'.実装が知っているが型定義が知らないプロパティ にアクセスしたときに出る。
「型がそもそも狭い」「ユニオン型で絞り込んでいない」「宣言が無い」のどれかを切り分け、対応する書き方を選ぶ。

よくある原因

  1. 型を明示していない: const user = await res.json() の戻り値が any ではなく狭い型に推論され、必要なプロパティが含まれていない
  2. ユニオン型のまま参照: type Shape = Circle | Squareshape.radius を読むと、Square に無いため拒否される(Narrowing 公式
  3. 型定義ファイル不在: import x from "old-lib"old-lib.d.ts が無く anyunknown 型になる
  4. グローバル拡張の不足: 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 公式ハンドブック のとおり、interfacetype で必要なプロパティを宣言する。
受け取った値はまず 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)にしておくと switchif で型が確定する。
詳細は 型の絞り込み を参照。

3. 型未提供パッケージに対応する

npm install --save-dev @types/lodash

公式型定義が無いなら自前で書く。
プロジェクトに types/old-lib.d.ts を作成し、tsconfig.jsoninclude に含める。

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.jsonincludesrc/types/** が含まれているかも確認する。

この記事は役立ちましたか?