Upgrade to Pro — share decks privately, control downloads, hide ads and more …

配列にまつわる型検査をしたら思ったより大変だった話

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

 配列にまつわる型検査をしたら思ったより大変だった話

Avatar for Kenta TSUNEMI

Kenta TSUNEMI

February 05, 2025
Tweet

More Decks by Kenta TSUNEMI

Other Decks in Technology

Transcript

  1. const ATTRS = ['id', 'type', 'class'] as const; という値があったときに、ある配列がこれらの要素を 過不足なく

    順不同で 含んでいるということを担保する型を作りたい。 3 実現したかったこと
  2. const ATTRS = ['id', 'class', 'type'] as const; type SugoiKata

    = ??? const ok1: SugoiKata = ['id', 'class', 'type'] // const ok2: SugoiKata = ['type', 'id', 'class'] // 順不同は許容 const ng1: SugoiKata = ['id', 'class'] // type が不足 const ng2: SugoiKata = ['id', 'class', 'type', 'for'] // for が余分 4 実現したかったこと
  3. 値の不足を検知できない const ATTRS = ['id', 'class', 'type'] as const; type

    Sugokunai = (typeof ATTRS)[number][]; // ^? type Sugokunai = ("id" | "class" | "type")[] const ok1: Sugokunai = ['id', 'class', 'type'] // const ok2: Sugokunai = ['type', 'id', 'class'] // const ng1: Sugokunai = ['id', 'class'] // 不足が検知できない const ng2: Sugokunai = ['id', 'class', 'type', 'for'] // : 期待通り : 期待通りでない 5 配列の型を作ってみる
  4. 順番の違いや重複でエラーになってしまう const ATTRS = ['id', 'class', 'type'] as const; type

    Sugokunai = typeof ATTRS; // ^? type Sugokunai = readonly ["id", "type", "class"] const ok1: Sugokunai = ['id', 'class', 'type'] // const ok2: Sugokunai = ['type', 'id', 'class'] // 順番が固定される const ng1: Sugokunai = ['id', 'class'] // const ng2: Sugokunai = ['id', 'class', 'type', 'for'] // : 期待通り : 期待通りでない 6 タプルにしてみる
  5. const ATTRS = ['id', 'class', 'type'] as const; type Diff<

    T extends readonly string[], U extends readonly string[] > = | Exclude<T[number], U[number]> | Exclude<U[number], T[number]> type HasSameElements< T extends readonly string[], U extends readonly string[] > = Diff<T, U> extends never ? true : false const ok1 = ['id', 'class', 'type'] as const; true satisfies HasSameElements<typeof ok1, typeof ATTRS> 7 どうにか頑張ってみる
  6. 過不足がなければ Diff<T> は never になる const ATTRS = ['id', 'class',

    'type'] as const; type Diff< T extends readonly string[], U extends readonly string[] > = | Exclude<T[number], U[number]> | Exclude<U[number], T[number]> type A = Diff<['id', 'class', 'type'], ATTRS> // ^? type A = never type B = Diff<['id', 'type'], ATTRS> // ^? type B = "class" type C = Diff<['id', 'type', 'class', 'for'], ATTRS> // ^? type C = "for" 8 Diff は何をしているか?
  7. Diff<T> の結果を boolean に変換した上で検査する // Diff<T> の結果を boolean に変換する型 type

    HasSameElements< T extends readonly string[], U extends readonly string[] > = Diff<T, U> extends never ? true : false // HasSameElements が false = 過不足がある場合は型エラー true satisfies HasSameElements< ['id', 'class', 'type'], typeof ATTRS > // OK true satisfies HasSameElements< ['id', 'class'], typeof ATTRS > // 型エラー 9 satisfies での検査
  8. const attrs = ['id', 'class', 'type'] as const; true satisfies

    HasSameElements<typeof attrs, typeof ATTRS> なんか微妙 type SugoiKata = ... const attrs: SugoiKata = ['type', 'id', 'class']; こういう形にしたかった... もっとシンプルにできないものか アイデアがあれば教えてください! 11 できたけど...