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くんは書き方によって推論ができたりできなかったりする。かわいい。