⌨️

TypeScriptで強制的に余剰プロパティチェックする型を作る

2021/02/07に公開
2

TL; DR

これを読むと解決します。
https://stackoverflow.com/questions/54775790/forcing-excess-property-checking-on-variable-passed-to-typescript-function

暇な人向け

TypeScriptの余剰プロパティチェックの話は結構有名だと思います。わざわざ繰り返す必要はないかと思うので,めっちゃよくまとまってるこちらの記事を参考にしてください。

https://qiita.com/uhyo/items/b1f806531895cb2e7d9a

今回解決したい問題を簡単にコードをまとめると,

interface T = {
    one?: string
    two?: string
}

const t = (arg: T): void => {
    console.log({arg})
}

t({one: 'hello', three: 'hello'}) // エラーになる

const a = {one: 'hello', three: 'hello'}

t(a) // エラーが出ない

Playground:
https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgCrIN4Chm+QexAgH4AuZAZzClAHMc8wB3fMy6urAXyywUKrIwyALzIAFHCi1yqAJTkAbvmAATUQD5MDXPxAV8AGwgA6Q-lriMU2lzndeYK4QjkA5AAsIh824A0Qh5QEK7Int6+drx6gnCimC7uXj74-oHBoeEpbjxYTnByQA

というコードについてを考えます。要は,例の最後の t(a) でエラーを出す, 言い換えると (この場合だとthreeという) 余分なプロパティを持っている場合にエラーにしたい,というのがゴールです。

できそうだけど考えるの面倒だなと思いぐぐったら先人の肩に乗ることができました。感謝。
https://stackoverflow.com/questions/54775790/forcing-excess-property-checking-on-variable-passed-to-typescript-function

このstackoverflowを参考に,このような型を導入します。

type StrictPropertyCheck<T, TExpected, TError> = T & (Exclude<keyof T, keyof TExpected> extends never ? {} : TError);

これだけで記事は終わりなんですが,せっかくなのですこしだけ解説すると,

  • TypeScriptの組み込み型 Exclude<T, U> はTがUにassignableだとTを,そうでなければUを返します。
  • keyof TTkey の名前のUnion Typeを返します。

よって, T の型に TExpected の余剰プロパティがあると, keyof TExpectedkeyof T が代入できないため, TError になるという仕組みです。

ではこれを導入するとどのように解決するのか見てみましょう。

interface T {
    one?: string
    two?: string
}

type StrictPropertyCheck<T, TExpected, TError> = T & (Exclude<keyof T, keyof TExpected> extends never ? {} : TError );

const t = <S extends T>(arg: StrictPropertyCheck<S, T, `S has excess property`>): void => {
    console.log(arg)
}

t({one: 'hello', three: 'hello'}) // エラー

const a = {one: 'hello', three: 'hello'}

t(a) // エラー

Playground:
https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgCrIN4Chm+QexAgH4AuZAZzClAHMc8wB3fMy6urAXyyzAE8ADigDKHBGAAKUfMKgCAwgAsICANYAeVABo0AUQAewiRAAmu1HqgyoAPmQBeNMgBkyABSGEAGwCupiA01CH58GDRdYNDwyyNVSFN7CANIEFMKZCIAN2hkYkwuZHJLa3woZABKAG5eBEIqZDBHZA0RZGTU9LRbdzgoWnIxGglpWWhFFXVWi10AAzalOAzkpAoMwRk5AVnbCvIs-GBTR3tsPGQ6kAp8bwgAOm98Wl7+iu5eMHcMQghyAHIVN5Hn9dGAlFAIL9kACIED8H8uG8sJcGnBmt8iP9AcDQeDIVjYcCeHxehUgA

確認すると,きちんとどちらでもエラーになってますね。

おわり

Discussion

mpywmpyw
type StrictPropertyCheck<T, TExpected> = Exclude<keyof T, keyof TExpected> extends never ? T : never;

function serializeBasicAnimalData<T extends Animal>(a: StrictPropertyCheck<T, Animal>) {
    // something
}

これだともう少しシンプル?

  • 型エラー表現の文字列を返す代わりに never にしました
  • T & {} とさせる代わりに Generics だけで T を表現できるようにしました