Saya ingin fungsi KV sederhana saya menghasilkan objek dengan key menjadi tipe literal dan tidak melebar ke string:

function KV<K extends Readonly<string>, V extends any>(key: K, value: V) {
  return { [key as K]: value };
}

type Target = {foo: number};
const t: Target = KV("foo", 43);

type Target2 = { bar: (bar: string) => {bar: string}};
const t2: Target2 = KV("bar", (bar: string) => ({ bar }));

Sayangnya, sementara K adalah generik didefinisikan sebagai literal, saya tidak dapat menemukan cara untuk memaksa pasangan nilai kunci yang dikembalikan untuk mengenali kunci sebagai literal.

enter image description here

Saya merasa agar ini berfungsi, saya harus lebih eksplisit tentang tipe pengembalian jadi saya mencoba fungsi KV seperti:

function KV2<O extends object, K extends Readonly<string> & keyof O, V extends any, KV extends O & [K: V]>(key: K, value: V): KV {
  return { [key as K]: value } as KV;
}

Harapan saya tinggi tapi sayangnya tidak ada bacon.

enter image description here

Sejak itu saya sudah mencoba beberapa varian lagi tetapi belum berhasil. Adakah yang lebih ahli dalam seni gelap tipe literal yang punya ide?

Kode dapat ditemukan di Taman Bermain ini

0
ken 10 Mei 2021, 19:05

1 menjawab

Jawaban Terbaik

Ok saya pikir saya sudah menjawab pertanyaan saya, jadi saya akan memposting jawaban saya di sini tetapi jika ada solusi yang lebih baik, saya terbuka untuk itu dan akan tetap membukanya untuk hari lain sebelum ditutup:

Solusi:

export function KV<
  K extends string, 
  V extends any, 
  O extends Record<K, V>
>(key: K, value: V): O {
  return { [key as K]: value } as O;
}

Ini kemudian memungkinkan saya untuk membuat nilai kunci dan ketika saya menggabungkannya, pengetikan penuh dipertahankan karena kuncinya adalah tipe literal. Jadi dalam contoh ini:

const kv = KV("foo", 43);
const kv2 = KV("bar", (bar: string) => ({ bar }));

const both = { ...kv, ...kv2 };

Jenis both memiliki semua informasi jenis kv dan kv2 yang dipertahankan.


Berdasarkan umpan balik yang bermanfaat dari @jcalz, saya telah menyesuaikan solusi saya dalam dua cara. Pertama, implementasi di atas telah disederhanakan menjadi:

export function KV<V extends any, K extends string>(key: K, value: V) {
  return { [key]: value } as Record<K, V>;
}

Ini akan menyimpulkan kunci dan nilai dan merupakan tanda tangan ringkas yang bagus. Namun, jika Anda ingin membatasi nilai ke definisi yang lebih sempit, ini tidak akan berfungsi karena akan mengharuskan kedua generik dinyatakan. Ini dapat dihindari dengan memisahkan konstruksi dengan fungsi, jadi saya memperkenalkan KV2 untuk kasus penggunaan ini:

export const KV2 = <V extends any>() => <K extends string>(key: K, value: V) => {
  return { [key]: value } as Record<K, V>;
};

Ini akan memungkinkan Anda -- sebagai contoh -- untuk menerima nilai kunci di mana nilainya adalah string yang harus dimulai dengan "kv-is-great":

const kv = 
KV2<`kv-is-great${string}`>()("foo", "kv-is-great-or-is-it?");

Dengan sendirinya ini mungkin tampak tidak terlalu berguna tetapi dalam komposisi yang lebih besar yang dapat membatasi nilai harus menjadi tambahan yang bermanfaat yang mengorbankan API yang sedikit lebih canggung.

0
ken 13 Mei 2021, 01:20