Тип индекса TypeScript слишком широкий
Извините, я уверен, что на это где-то ответили, но я не уверен, что гуглить. Пожалуйста, отредактируйте мой вопрос, если термины в заголовке неверны.
У меня примерно так:
type RowData = Record<string, unknown> & {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значение было присвоено столбцу:
то есть:
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, но только одна клавиша, и она никогда не меняется».
Ответы
Если вы ожидаете , что ключи Tбыть объединением из литералов , как "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);
}
},
]
});