typescript - Dapatkan penyelesaian untuk tipe kompleks


Saya ingin sebuah cara untuk melewatkan myG ke Foo dan mendapatkan penyelesaian otomatis (di vscode) untuk Foo.get dan untuk argumen konstruktor OBJ/KEY_OF_OBJ, saya bisa membuatnya bekerja sehingga saya mendapatkan penyelesaian otomatis untuk Foo.get atau untuk OBJ/KEY_OF_OBJ, tapi tidak semuanya sekaligus.

interface G {
  [key: string]: string;
}
interface GE<T extends G> {
  [key: string]: T;
}

class Foo<T extends G, K extends GE<T>> {
  constructor(OBJ: K, KEY_OF_OBJ: keyof K) {}
  get(keyOfG: keyof T) {}
}

interface myG extends G {
  d: string;
  e: string;
}

const foo = new Foo<myG>(
  {
    a: {
      d: '',
      e: ''
    },
    c: {
      d: '',
      e: ''
    }
  },
  'c'
);
foo.get('');

edit: harus mendeklarasikan Foo seperti ini new Foo<myG, {a: myG, c: myG}> juga diterima, satu-satunya masalah saya dengan mendeklarasikannya seperti ini adalah, bahwa Foo.get tidak mendapatkan autocompletion yang disebabkan karena ts menganggapnya sebagai hal string, jika saya menghapus G, itu bekerja, tapi saya ingin T memiliki pasangan key: stringValue, saya rasa masalah saya cukup terselesaikan, kecuali ada cara untuk membatasi T hanya memiliki nilai string.

Jawaban #1

Saya akan mengasumsikan bahwa Anda tidak keberatan membuat instance Foo dengan secara manual menentukan kedua parameter tipe generik T dan K:

const foo = new Foo<myG, { a: myG, c: myG }>(
    {
        a: { d: "", e: "" },
        c: { d: "", e: "" }
    }, "c"
);

Jadi pertanyaan Anda adalah: bagaimana kita bisa mendapatkan autocompletion di IDE kita untuk kunci literal yang diketahui "d" dan "e" dari myG, ketika tanda indeks string menyerap "d" dan "e" ke dalamnya? Artinya, karena "d" | "e" | string dievaluasi menjadi hanya string, bagaimana kita membuat IDE "mengingat" "d" dan "e"?

Ada masalah terbuka (Microsoft/TypeScript#33471) tentang ini, jadi belum ada jawaban yang mudah dan jelas. Jawaban kompleks dan/atau tidak jelas apa yang ada? Ini salah satunya.

Pertama, gunakan solusi yang ada untuk mendapatkan kunci literal yang diketahui dari suatu tipe dengan tanda indeks:

type KnownKeys<T> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U } ? U : never;

Mari kita verifikasi bahwa itu melakukan hal yang benar dengan myG:

type KnownKeysOfMyG = KnownKeys<myG>
// type KnownKeysOfMyG = "d" | "e"

Terlihat bagus. Sekarang, kita bisa mengubah tanda tangan get() dari Foo untuk menerima KnownKeys<T>:

get(keyOfG: Extract<KnownKeys<T>, string>): void;

Jika kita masih ingin menerima nilai string sembarangan di get(), solusi sementara di sini adalah menambahkan tanda tangan overload kedua untuk get() yang menerima string:

  get(keyOfG: Extract<KnownKeys<T>, string>): void;
  get(keyOfG: string): void;
  get(keyofG: string) {
     // impl
  }

Ini masih menerima semua nilai string:

foo.get(""); // okay
foo.get("d"); // okay
foo.get("e"); // okay
foo.get("fkjksjfksjfkds"); // okay

Tapi saran otomatis sekarang berfungsi:

Oke, semoga itu membantu; semoga berhasil!

Tautan playground ke kode

Jawaban #2

Saya tidak berpikir TypeScript memiliki cara untuk mengetahui itu saat waktu kompilasi.

Anda pada dasarnya mengatakan bahwa G dapat memiliki properti dari string apa pun, jadi myG memperluas itu dan memiliki 2 properti yang diperlukan d dan e, tetapi masih mungkin untuk menambahkan properti baru apa pun, dengan nama apa pun, dari tipe string. Jadi pada waktu kompilasi Typescript tidak bisa mengetahui semua properti yang ditetapkan dari tipe string.

Satu-satunya cara untuk mendapatkan penyelesaian adalah jika Anda mendefinisikan sekumpulan string kecil pada antarmuka G.

interface G {
 a ?: string;
 b ?: string;
 c ?: string;
 d ?: string;
 e ?: string;
}

Dengan cara ini get hanya dapat menerima argumen dari "a" | "b" | "c" | "d" | "e"