TypeScriptの引数を分割代入したときの ‘A’ is assignable to the constraint of type ‘T’, but ‘T’ could be instantiated with a different subtype of constraint ‘B’ みたいなエラー

T型の引数を受け取ってそのまま返す関数だが、返り値の型をTにすると型エラーになる。

const f1 = <T extends { user: { id: number } }>({ user, ...rest }: T): T => {
  return {
    user,
    ...rest,
  }
}
Type '{ id: number; } & Omit<T, "id">' is not assignable to type 'T'.
  '{ id: number; } & Omit<T, "id">' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ id: number; }'.(2322)

このエラーメッセージが難しい。 T extends { user: { id: number } } に注目する。extendsだからTはたとえば

type T1 = {
  user: {
    id: number;
    name: string;
  }
}

かもしれない(狭い型)。そうだとしても、関数内でのuserの型は { id: number } だ。nameがあることにはならない。TypeScriptならわかるかと思ったがわからないらしい。

Tに何を渡されようと返り値のuserの型は { id: number } (広い型)なので、それを T["user"] (狭い型)に渡しても要求を満たせないことがある。だから型エラーになる。

という話はここに書いてあるのだが
https://github.com/microsoft/TypeScript/issues/33579#issuecomment-534789400

このように書くと引数の型を維持できる。

const f2 = <T extends { user: { id: number } }>({ user, ...rest }: T): Omit<T, "user"> & { user: T["user"] } => {
  return {
    user,
    ...rest,
  }
}

const b = f2({user:{id: 1, name: "chao", premium: false}})
const p = b.user.premium // アクセス可能

TypeScriptくんは書き方によって推論ができたりできなかったりする。かわいい。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です