如何向 TypeScript 解釋泛型類中的變量總是字串
我有以下的類別和介面定義:
import { Component } from 'react';
type KeysMatching<T, V> = {
[K in keyof T]-?: T[K] extends V ? K : never
}[keyof T];
interface SearchListProps<T extends object, K extends KeysMatching<T, string>> {
data: T[];
sort_and_filter_key: K;
}
class SearchList<ItemDataT extends object, K extends KeysMatching<ItemDataT, string>> extends Component<SearchListProps<ItemDataT, K>> {
constructor(props: SearchListProps<ItemDataT, K>) {
super(props);
const value = props.data[0][props.sort_and_filter_key]; // ItemDataT[K] -> should be string
}
}
// exmple case:
interface DataExample {
name: string,
city: string,
id: number,
content: object
// etc
}
const example_data: DataExample[] = [
{
name: "asd",
city: "bcn",
id: 1,
content: {}
},
{
name: "dkg",
city: "nld",
id: 2,
content: {}
}
];
<SearchList data={example_data} sort_and_filter_key={"city"}></SearchList>
如你在程式碼中看到的註解,TypeScript 認為 value 的類型是 ItemDataT[K],但它應該是 string。
為什麼?因為屬性 sort_and_filter_key 應該只允許值為字串的鍵,這是根據以下泛型表達式:K extends KeysMatching<T, string>。但是 TypeScript 似乎不理解這一點。
自動完成功能似乎能理解,請見下圖:
由於這張圖清楚地只允許兩個保證為字串的鍵。
我該如何讓 tsc 也理解這個?
如果有任何不清楚的地方,請告訴我,這樣我可以修改問題。
答案#1
看起來這個問題是 Typescript 在泛型類型推斷能力上的限制——Typescript 不會追蹤所有可推斷的約束條件。確實這樣做是有一些取捨的。
對於具體類型,推斷如預期般運作:
const props: SearchListProps<DataExample> = {
data: example_data,
sort_and_filter_key: 'city',
};
const ok_string = props.data[0][props.sort_and_filter_key];
在遇到泛型類型的情況下,我們需要手動給 TypeScript 引擎提示我們究竟感興趣的是什麼:
interface SearchListProps<T> {
data: (T & Record<KeysMatching<T, string>, string>)[];
sort_and_filter_key: KeysMatching<T, string>;
}
有趣的是,我們可以將 T 與其他任何東西做交集——例如 Record<KeysMatching<T, string>, number>,但這樣一來,我們就無法用具體類型初始化 SearchListProps。
一個固定和簡化的版本:https://stackblitz.com/edit/react-ts-x4jdez
順便提一下,props.data[0][props.sort_and_filter_key] 不安全(noUncheckedIndexedAccess)。
答案#2
我們也可以給 ItemDataT 添加約束 Record<K, string>,其中 K 只是被約束為 string。
class SearchList<
ItemDataT extends Record<K, string>,
K extends string
> extends Component<SearchListProps<ItemDataT, K>> {
constructor(props: {
data: ItemDataT[],
sort_and_filter_key: K
}) {
/* ... */
}
}
當我們現在存取 props.data[0][props.sort_and_filter_key] 時,TypeScript 仍然將類型顯示為 ItemDataT[K]。但是 TypeScript 其實知道這次這一定是 string。所有字串方法都可以使用。類型只是顯示為 「錯誤」,但行為是正確的。
const value = props.data[0][props.sort_and_filter_key];
value.charAt(0)
// this was compile time error before
遊樂場
