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

TypeScript 5.0 const 型パラメータの使い道

TypeScript 5.0 const 型パラメータの使い道

More Decks by NearMeの技術発表資料です

Other Decks in Programming

Transcript

  1. 2 Type<Challenge[]> https://github.com/type-challenges/type-challeng es TypeScript tips and Tricks with Matt

    https://www.youtube.com/watch?v=hBk4nV7q6- w TypeScript ⊇ JavaScript // これは TypeScript function add1(a: number, b: number): number { return a + b; } // 実はこれも TypeScript function add2(a, b) { return a + b; } https://typescript-jp.gitbook.io/deep-dive/
  2. 3 • JavaScript で型システムが使える(実際に型を利用するかどうかは開発者の自由) • JavaScript の将来のバージョンで計画されている機能が使える なぜ TypeScript なのか?

    → プログラムを実行する前にエラーが検出できる → 型はそれ自体が良質なドキュメントになる → オプショナルチェーン → デコレータ → etc. a?.b?.c // オプショナルチェーン a?.b?.c // オプショナルチェーン @decorator class A {...} // デコレータ https://trends.google.co.jp/trends/explore?date=2013-03-25%202023-03-25&q=TypeScript
  3. 5 型推論 vs. 型宣言 // 型宣言 let foo: number =

    123; foo = “456”; // 型推論 let bar = 123; bar = “456”; https://typescript-jp.gitbook.io/deep-dive/getting-started/why-typescript
  4. 6 構造型 ≠ 宣言型 interface Point2D { x: number; y:

    number; } interface Point3D { x: number; y: number; z: number; } const point2D: Point2D = { x: 0, y: 10 } const point3D: Point3D = { x: 0, y: 10, z: 20 } function iTakePoint2D(point: Point2D) { /* なんらかの処理 */ } iTakePoint2D(point2D); // 全く同じ構造なので問題なし iTakePoint2D(point3D); // 追加のプロパティがあっても問題なし iTakePoint2D({ x: 0 }); https://typescript-jp.gitbook.io/deep-dive/getting-started/why-typescript
  5. 7 ジェネリック型 // ジェネリック型なし class QueueNumber { private data =

    []; push(item: number) { super.push(item); } pop(): number { return this.data.shift(); } } // ジェネリック型あり class Queue<T> { private data: T[] = []; push(item: T) { this.data.push(item); } pop(): T | undefined { return this.data.shift(); } } const queue = new Queue<number>(); queue.push(0); queue.push("1"); https://typescript-jp.gitbook.io/deep-dive/getting-started/why-typescript
  6. 12 リテラル型 // let または const でプリミティブ型を宣言 let version =

    1; version === 2; const readonlyVersion = 1; readonlyVersion === 2; // const アサーション「あり」または「なし」でオブジェクトを宣言 const user = { name: "yujiosaka" }; user.name === "Yuji Isobe"; const reaonlyUser = { name: "yujiosaka" } as const; reaonlyUser.name === "Yuji Isobe"; // チェスゲームの作成 type File = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H"; type Rank = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; let rank: Rank = 0;
  7. 14 Easy: If type If<C, T, F> = C extends

    true ? T : F https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.md
  8. 15 Medium: Deep readonly type DeepReadonly<T> = { readonly [key

    in keyof T]: keyof T[key] extends never ? T[key] : DeepReadonly<T[key]> } https://github.com/type-challenges/type-challenges/blob/main/questions/00009-medium-deep-readonly/README .md
  9. 16 Hard: camelize type Camelize<T> = T extends object ?

    { [K in keyof T as CamelCase<K>]: T[K] extends unknown[] ? ( CamelizeArray<T[K]> ) : Camelize<T[K]> } : T type CamelizeArray<T> = T extends [infer First, ...infer Rest] ? ( [Camelize<First>, ...CamelizeArray<Rest>] ) : T type CamelCase<T> = T extends `${infer First}${'_'}${infer Second}${infer Rest}` ? ( `${First}${CamelCase<`${Uppercase<Second>}${Rest}`>}` ) : T https://github.com/type-challenges/type-challenges/blob/main/questions/01383-hard-camelize/README.md
  10. 17 Extreme: Query string parser type GetKeyAndValue<S extends string, O>

    = S extends `${infer first}=${infer rest}` ? first extends keyof O ? O[first] extends any[] ? rest extends O[first][number] ? O : { [key in (keyof O) | first]: key extends first ? [...O[first], rest] : O[key] } : rest extends O[first] ? O : { [key in (keyof O) | first]: key extends first ? [O[first], rest] : O[key] } : { [key in (keyof O) | first]: key extends first ? rest : key extends keyof O ? O[key] : never } : S extends "" ? O : { [key in (keyof O) | S]: key extends S ? true : key extends keyof O ? O[key] : never } type ParseQueryString<S extends string, O = {}> = S extends `${infer first}&${infer rest}` ? ParseQueryString<rest, GetKeyAndValue<first, O>> : GetKeyAndValue<S, O> https://github.com/type-challenges/type-challenges/blob/main/questions/00151-extreme-query-string-parser/README.md
  11. 18

  12. 19 変更点 • デコレータ • const 型パラメータ • extends 複数設定ファイル対応

    • enums 型安全化 • etc. https://github.com/microsoft/TypeScript/issues/30680#issuecomment-1161619377 https://github.com/microsoft/TypeScript/issues/30680#issuecomment-1161619377
  13. 21 TypeScript < 5.0 type HasNames = { names: readonly

    string[] }; function getNamesExactly<T extends HasNames>(arg: T): T["names"] { return arg.names; } // as const アサーションなし const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"] }); // as const アサーションあり const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"] } as const);
  14. 22 TypeScript >= 5.0 type HasNames = { names: readonly

    string[] }; function getNamesExactly<const T extends HasNames>(arg: T): T["names"] { return arg.names; } // as const アサーションあり const names3 = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
  15. 27 設定が柔軟すぎる const sequelizeRevision = new SequelizeRevision(sequelize, { UUID: false,

    useJsonDataType: false, underscored: false, underscoredAttributes: false, revisionAttribute: "revision", userModel: "User", userIdAttribute: "userId", enableRevisionChangeModel: false, }); const [Revision] = sequelizeRevision.defineModels(); interface Revision extends Model<...> { id: number; document: string; documentId: number; operation: string; revision: number; userId: number; createdAt: Date; updatedAt: Date; } 設定に応じて全く違う変更履歴モデル(Revision, RevisionChange)が定義される
  16. 28 設定が柔軟すぎる const sequelizeRevision = new SequelizeRevision(sequelize, { UUID: true,

    useJsonDataType: true, underscored: true, underscoredAttributes: true, revisionAttribute: "version", revisionIDAttribute: "versionId", // userModel: "User", // userIdAttribute: "userId", enableRevisionChangeModel: true, }); const [Revision, RevisionChange] = sequelizeRevision.defineModels(); interface Revision extends Model<...> { id: string; document: object; document_id: string; operation: string; version: number; // userId declaration is gone created_at: Date; updated_at: Date; } interface RevisionChange extends Model<...> { id: string; version_id: string; path: object; document: object; diff: object; created_at: Date; updated_at: Date; } 設定に応じて全く違う変更履歴モデル(Revision, RevisionChange)が定義される
  17. 30 静的なフィールドを定義する class SequelizeRevision<O extends SequelizeRevisionOptions> { constructor( sequelize: Sequelize,

    options?: O ) { // Initialize the instance } public defineModels(): [ModelStatic<Revision<O>>] { const Revision = this.sequelize.define<Revision<O>>( // Define Revision model return [Revision]; } // Other functions } interface Revision<O extends SequelizeRevisionOptions> extends Model< InferAttributes<Revision<O>>, InferCreationAttributes<Revision<O>> > { // id: string | number; model: string; // document: string | object; operation: string; // revision: number; // documentId: string | number; // userId: string | number; // createdAt: Date; // updatedAt: Date; }
  18. 31 設定に応じて型を変更する class SequelizeRevision<O extends SequelizeRevisionOptions> { constructor( sequelize: Sequelize,

    options?: O ) { // Initialize the instance } public defineModels(): [ModelStatic<Revision<O>>] { const Revision = this.sequelize.define<Revision<O>>( // Define Revision model return [Revision]; } // Other functions } interface Revision<O extends SequelizeRevisionOptions> extends Model< InferAttributes<Revision<O>>, InferCreationAttributes<Revision<O>> > { id: O["UUID"] extends true ? string : number; model: string; document: O["useJsonDataType"] extends true ? object : string; operation: string; // revision: number; // documentId: string | number; // userId: string | number; // createdAt: Date; // updatedAt: Date; }
  19. 32 動的にフィールド名を変更する interface Revision<O extends SequelizeRevisionOptions> extends Model< InferAttributes<Revision<O>>, InferCreationAttributes<Revision<O>>

    > { id: O["UUID"] extends true ? string : number; model: string; document: O["useJsonDataType"] extends true ? object : string; operation: string; [Revision in O["revisionAttribute"] extends string ? O["revisionAttribute"] : "revision"]: number; // documentId: string | number; // userId: string | number; // createdAt: Date; // updatedAt: Date; } class SequelizeRevision<O extends SequelizeRevisionOptions> { constructor( sequelize: Sequelize, options?: O ) { // Initialize the instance } public defineModels(): [ModelStatic<Revision<O>>] { const Revision = this.sequelize.define<Revision<O>>( // Define Revision model return [Revision]; } // Other functions }
  20. 33 設定に応じてフィールド名を変更する type RevisionAttributes<O extends SequelizeRevisionOptions> = { id: O["UUID"]

    extends true ? string : number; model: string; document: O["useJsonDataType"] extends true ? object : string; operation: string; // documentId: string | number; // userId: string | number; // createdAt: Date; // updatedAt: Date; } & { [Revision in O["revisionAttribute"] extends string ? O["revisionAttribute"] : "revision"]: number; } type RevisionCreationAttributes<O extends SequelizeRevisionOptions> = Optional<RevisionAttributes<O>, "id">; type Revision<O extends SequelizeRevisionOptions> = Model<RevisionAttributes<O>, RevisionCreationAttributes<O>> & RevisionAttributes<O>; class SequelizeRevision<O extends SequelizeRevisionOptions> { constructor( sequelize: Sequelize, options?: O ) { // Initialize the instance } public defineModels(): [ModelStatic<Revision<O>>] { const Revision = this.sequelize.define<Revision<O>>( // Define Revision model return [Revision]; } // Other functions }
  21. 34 設定に応じてフィールド名をキャメルケース化する type RevisionAttributes<O extends SequelizeRevisionOptions> = { id: O["UUID"]

    extends true ? string : number; model: string; document: O["useJsonDataType"] extends true ? object : string; operation: string; } & { [Revision in O["revisionAttribute"] extends string ? O["revisionAttribute"] : "revision"]: number; } & { [DocumentId in O["underscoredAttributes"] extends true ? CamelToSnakeCase<"documentId"> : "documentId"]: O["UUID"] extends true ? string : number; } & { [UserId in O["userModel"] extends string ? O["userIdAttribute"] extends string ? O["underscoredAttributes"] extends true ? CamelToSnakeCase<O["userIdAttribute"]> : O["userIdAttribute"] : O["underscoredAttributes"] extends true ? CamelToSnakeCase<"userId"> : "userId" : never]: O["UUID"] extends true ? string : number; } & { [CreatedAt in O["underscoredAttributes"] extends true ? CamelToSnakeCase<"createdAt"> : "createdAt"]: Date; } & { [UpdatedAt in O["underscoredAttributes"] extends true ? CamelToSnakeCase<"updatedAt"> : "updatedAt"]: Date; } type RevisionCreationAttributes<O extends SequelizeRevisionOptions> = Optional<RevisionAttributes<O>, "id">; type Revision<O extends SequelizeRevisionOptions> = Model<RevisionAttributes<O>, RevisionCreationAttributes<O>> & RevisionAttributes<O>; [UserId in O["userModel"] extends string ? O["userIdAttribute"] extends string ? O["underscoredAttributes"] extends true ? CamelToSnakeCase<O["userIdAttribute"]> : O["userIdAttribute"] : O["underscoredAttributes"] extends true ? CamelToSnakeCase<"userId"> : "userId" : never]: O["UUID"] extends true ? string : number; class SequelizeRevision<O extends SequelizeRevisionOptions> { constructor( sequelize: Sequelize, options?: O ) { // Initialize the instance } public defineModels(): [ModelStatic<Revision<O>>] { const Revision = this.sequelize.define<Revision<O>>( // Define Revision model return [Revision]; } // Other functions }
  22. 35 完成 import type { Model, Optional } from "sequelize";

    import type { SequelizeRevisionOptions } from "./options"; import type { CamelToSnakeCase } from "./util-types"; type TimestampAttributes<O extends SequelizeRevisionOptions> = { [CreatedAt in O["underscoredAttributes"] extends true ? CamelToSnakeCase<"createdAt"> : "createdAt"]: Date; } & { [UpdatedAt in O["underscoredAttributes"] extends true ? CamelToSnakeCase<"updatedAt"> : "updatedAt"]: Date; }; type MetaDataAttributes<O extends SequelizeRevisionOptions> = { [Field in keyof O["metaDataFields"]]: any; }; type RevisionAttributes<O extends SequelizeRevisionOptions> = { id: O["UUID"] extends true ? string : number; model: string; document: O["useJsonDataType"] extends true ? object : string; operation: string; } & { [Revision in O["revisionAttribute"] extends string ? O["revisionAttribute"] : "revision"]: number; } & { [DocumentId in O["underscoredAttributes"] extends true ? CamelToSnakeCase<"documentId"> : "documentId"]: O["UUID"] extends true ? string : number; } & { [UserId in O["userModel"] extends string ? O["userIdAttribute"] extends string ? O["underscoredAttributes"] extends true ? CamelToSnakeCase<O["userIdAttribute"]> : O["userIdAttribute"] : O["underscoredAttributes"] extends true ? CamelToSnakeCase<"userId"> : "userId" : never]: O["UUID"] extends true ? string : number; } & MetaDataAttributes<O> & TimestampAttributes<O>; type RevisionCreationAttributes<O extends SequelizeRevisionOptions> = Optional<RevisionAttributes<O>, "id">; export type Revision<O extends SequelizeRevisionOptions> = Model<RevisionAttributes<O>, RevisionCreationAttributes<O>> & RevisionAttributes<O>; type RevisionChangeAttributes<O extends SequelizeRevisionOptions> = { id: O["UUID"] extends true ? string : number; path: string; document: O["useJsonDataType"] extends true ? object : string; diff: O["useJsonDataType"] extends true ? object : string; } & { [RevisionId in O["revisionIdAttribute"] extends string ? O["underscoredAttributes"] extends true ? CamelToSnakeCase<O["revisionIdAttribute"]> : O["revisionIdAttribute"] : O["underscoredAttributes"] extends true ? CamelToSnakeCase<"revisionId"> : "revisionId"]: O["UUID"] extends true ? string : number; } & TimestampAttributes<O>; type RevisionChangeCreationAttributes<O extends SequelizeRevisionOptions> = Optional<RevisionChangeAttributes<O>, "id">; export type RevisionChange<O extends SequelizeRevisionOptions> = Model<RevisionChangeAttributes<O>, RevisionChangeCreationAttributes<O>> & RevisionChangeAttributes<O>; class SequelizeRevision<O extends SequelizeRevisionOptions> { constructor( sequelize: Sequelize, options?: O ) { // Initialize the instance } public defineModels(): O["enableRevisionChangeModel"] extends true ? [ModelStatic<Revision<O>>, ModelStatic<RevisionChange<O>>] : [ModelStatic<Revision<O>>] { const Revision = this.sequelize.define<Revision<O>>( // Define Revision model const RChange = this.sequelize.define<RevisionChange<O>>( // Define RevisionChange model return [Revision, RChange]; } // Other functions }
  23. 37 あと一歩 const sequelizeRevision = new SequelizeRevision(sequelize, { underscored: true,

    underscoredAttributes: true, userModel: "User", userIdAttribute: "user_id", enableRevisionChangeModel: true, }); const [Revision] = sequelizeRevision.defineModels(); const revision = (await Revision.findOne()) || new Revision(); revision.document_id = 1; revision.user_id = 2; // トランスパイルが通るはず revision.invalid_attribute = 3;
  24. 38 こうすれば問題ない const sequelizeRevision = new SequelizeRevision(sequelize, { readonly underscored:

    true, readonly underscoredAttributes: true, readonly userModel: "User", readonly userIdAttribute: "user_id", readonly enableRevisionChangeModel: true, }); const [Revision] = sequelizeRevision.defineModels(); const revision = (await Revision.findOne()) || new Revision(); revision.document_id = 1; revision.user_id = 2; revision.invalid_attribute = 3; const sequelizeRevision = new SequelizeRevision(sequelize, { underscored: true, underscoredAttributes: true, userModel: "User", userIdAttribute: "user_id", enableRevisionChangeModel: true, } as const); const [Revision] = sequelizeRevision.defineModels(); const revision = (await Revision.findOne()) || new Revision(); revision.document_id = 1; revision.user_id = 2; revision.invalid_attribute = 3;
  25. 41 型パラメータに const 制約を付与する class SequelizeRevision<O extends SequelizeRevisionOptions> { constructor(

    sequelize: Sequelize, options?: O ) { // Initialize the instance } class SequelizeRevision<const O extends SequelizeRevisionOptions> { constructor( sequelize: Sequelize, options?: O ) { // Initialize the instance }
  26. 42 const アサーションを取り払っても動作する const sequelizeRevision = new SequelizeRevision(sequelize, { underscored:

    true, underscoredAttributes: true, userModel: "User", userIdAttribute: "user_id", enableRevisionChangeModel: true, } as const); const [Revision] = sequelizeRevision.defineModels(); const revision = (await Revision.findOne()) || new Revision(); revision.document_id = 1; revision.user_id = 2; revision.invalid_attribute = 3; const sequelizeRevision = new SequelizeRevision(sequelize, { underscored: true, underscoredAttributes: true, userModel: "User", userIdAttribute: "user_id", enableRevisionChangeModel: true, }); const [Revision] = sequelizeRevision.defineModels(); const revision = (await Revision.findOne()) || new Revision(); revision.document_id = 1; revision.user_id = 2; revision.invalid_attribute = 3;