#typescript #generics #keyof
#typescript #ключ
Вопрос:
Извиняюсь, я уверен, что на этот вопрос где-то был дан ответ, но я не уверен, что искать в Google. Пожалуйста, отредактируйте мой вопрос, если термины в названии неверны.
У меня есть что-то вроде этого:
type RowData = Record<string, unknown> amp; {id: string};
type Column<T extends RowData, K extends keyof T> = {
key: K;
action: (value: T[K], rowData: T) => void;
}
type RowsAndColumns<T extends RowData> = {
rows: Array<T>;
columns: Array<Column<T, keyof T>>;
}
И TypeScript должен иметь возможность определять типы action
функций, изучая форму строк и то, какое key
значение было присвоено столбцу:
ie:
function myFn<T extends RowData>(config: RowsAndColumns<T>) {
}
myFn({
rows: [
{id: "foo",
bar: "bar",
bing: "bing",
bong: {
a: 99,
b: "aaa"
}
}
],
columns: [
{
key: "bar",
action: (value, rowData) => {
console.log(value);
}
},
{
key: "bong",
action: (value, rowData) => {
console.log(value.a); //Property 'a' does not exist on type 'string | { a: number; b: string; }'.
}
}
]
});
Проблема в том, что TypeScript, похоже, выводит тип value ( value: T[K]
) как «типы всех, доступных для всех ключей T», а не использует только ключ, указанный в объекте столбца.
Почему TypeScript делает это и как я могу это решить?
Что могло бы дать хороший ответ, так это определение некоторых конкретных терминов и понятий.
Я полагаю, я хочу изменить свой K extends keyof T
на что-то вроде «K — это ключ T, но только один ключ, и он никогда не меняется».
Комментарии:
1. Не могу проверить это прямо сейчас, но
key: "bar" as const
вместо того, чтобы простоkey: "bar"
помочь?2. @Cerberus нет — я пробовал это 🙂
Ответ №1:
Если вы ожидаете, что ключи of T
будут объединением литералов типа like "bong" | "bing" | ...
(и не только string
), тогда вы можете выразить тип, который сам по себе является объединением Column<T, K>
для каждого ключа K
keyof T
.
Обычно я делаю это с помощью немедленной индексации (поиска) в сопоставленный тип:
type SomeColumn<T extends RowData> = {
[K in keyof T]-?: Column<T, K>
}[keyof T]
но вы также можете сделать это с помощью распределительных условных типов:
type SomeColumn<T extends RowData> = keyof T extends infer K ?
K extends keyof T ? Column<T, K> : never : never;
В любом случае, ваш RowsAndColumns
тип будет использовать SomeColumn
вместо Column
:
type RowsAndColumns<T extends RowData> = {
rows: Array<T>;
columns: Array<SomeColumn<T>>;
}
И это позволяет вашему желаемому варианту использования работать так, как ожидалось, без ошибок компиляции:
myFn({
rows: [
{
id: "foo",
bar: "bar",
bing: "bing",
bong: {
a: 99,
b: "aaa"
}
}
],
columns: [
{
key: "bar",
action: (value, rowData) => {
console.log(value);
}
},
{
key: "bong",
action: (value, rowData) => {
console.log(value.a);
}
},
]
});
Комментарии:
1. Большое вам спасибо! Что это
-?
за синтаксис — я никогда этого не видел.2. Он удаляет необязательный модификатор из свойств отображаемого типа (в противном случае вы получаете странные
undefined
s в типах значений); см. Эту документацию3. Ваши ответы потрясающие, большое вам спасибо! :3
4. комментарий, чтобы сказать, что это не работает с TS 4.8.4
5. @AbeCaymo Я только что посмотрел и не вижу никакой очевидной проблемы; не могли бы вы уточнить, что конкретно «не работает»?