TypeScript: "Tipe bersifat generik dan hanya dapat diindeks untuk membaca. (2862)"
Bayangkan contoh fungsi JS ini yang ingin saya tambahkan tipenya:
const remap = (obj) => {
const mapped = {};
Object.keys(obj).forEach((key) => {
mapped[key] = !!key;
});
return mapped;
};
Saya mencoba menambahkan tipe melalui generik (di playground TS ini), tetapi saya terus mendapatkan kesalahan ini:
Type 'Mapped<T>' is generic and can only be indexed for reading.(2862)
type Mapped<T> = {
[K in keyof T]?: boolean;
};
const remap = <T extends Record<string, unknown>>(
obj: T
) => {
const mapped: Mapped<T> = {};
Object.keys(obj).forEach((key) => {
mapped[key] = !!key; // Type 'Mapped<T>' is generic and can only be indexed for reading.(2862)
});
return mapped;
};
Saya ingin memahami mengapa TS tidak mengizinkan saya menulis ke objek dengan tipe generik ini, dan apakah ada cara lain untuk mengatasinya. Saya mengharapkan TS untuk memahami tipe dari mapped dan mengizinkan saya menulis ke dalamnya, tetapi itu tidak terjadi.
Apakah satu-satunya cara agar TS menerima ini adalah dengan menggunakan as saat mengembalikan?
const remapWithAs = <T extends Record<string, unknown>>(
obj: T
) => {
const mapped: Record<string, boolean> = {};
Object.keys(obj).forEach((key) => {
mapped[key] = !!key;
});
return mapped as Mapped<T>; // Is this my only option?
};
Jawaban #1
Alasan mendasar dari kesalahan ini adalah bahwa Object.keys(x) dideklarasikan dalam pustaka TS untuk mengembalikan string[] dan bukan sesuatu seperti (keyof typeof x)[]. Ini disengaja; lihat Mengapa Object.keys tidak mengembalikan tipe keyof di TypeScript?. Jadi ketika Anda mengindeks mapped[key], Anda melakukannya dengan sebuah kunci dari string dan tidak selalu dengan kunci dari Mapped<T>. Oleh karena itu, secara teknis tidak aman untuk menulis boolean ke dalamnya, karena mungkin Anda menulis ke kunci yang tidak diketahui oleh Mapped<T>, dan Anda tidak bisa yakin bahwa kunci itu akan menerima boolean. Anda perlu membuat Mapped<T> hanya menjadi {[k: string]: boolean} (yang berarti T tidak diperlukan) atau Anda perlu memastikan bahwa apa yang Anda lakukan aman.
Perlu dicatat bahwa TypeScript akan membiarkan Anda membaca boolean dari mapped[key], meskipun secara teknis ini juga tidak aman:
Object.keys(obj).forEach((key) => {
const test = mapped[key]; // boolean | undefined
});
Begitulah TypeScript. Bagaimanapun, itulah sebabnya kamu mendapatkan pesan kesalahan bahwa Mapped<T> hanya bisa diindeks (dengan string) untuk dibaca. Dulu hanya dikatakan bahwa kamu sama sekali tidak bisa mengindeks Mapped<T> dengan string, tetapi karena itu jelas tidak benar seperti yang ditunjukkan di atas, mereka mengubah pesan kesalahan ke kata-kata baru. Lihat microsoft/TypeScript#47357 untuk informasi lebih lanjut.
Bagaimanapun, pendekatan normal dengan melakukan assert adalah dengan mengatakan bahwa Anda yakin bahwa Object.keys(obj) akan mengembalikan (keyof T)[], meskipun TypeScript meragukan bahwa mungkin ada kunci lain yang akan muncul. Jika Anda melakukan itu:
const remap = <T extends Record<string, unknown>>(
initialState: T
) => {
const mapped: Mapped<T> = {};
(Object.keys(initialState) as (keyof T)[]).forEach(key => {
mapped[key] = !!key; // okay
});
return mapped;
};
maka itu bekerja seperti yang tertulis. TypeScript dengan senang hati mengizinkan bahwa mapped[key] adalah tipe Mapped<T>[keyof Mapped<T>] yang adalah boolean | undefined, dan oleh karena itu itu akan menerima boolean.
Tautan playground ke kode
