typescript - 使用原类型的键索引映射类型
我正在编写一个解析 CSV 的函数,并返回一个类型化对象的数组。看起来是这样的。
type KeyToTransformerLookup<T> = {
[K in keyof T as T[K] extends string ? never : K]: (
stringValue: string
) => T[K];
};
export default function csvToArray<T>(
csv: string,
keys: Array<keyof T>,
keyToTransformerLookup: KeyToTransformerLookup<T>,
ignoreFirstLine = false
): T[] {
const rows = csv.split(/\n(?=.)/);
if (ignoreFirstLine) {
rows.shift();
}
return rows.map((row) => {
const values = row.split(/(?!\B"[^"]*),(?![^"]*"\B)/g);
const rowObject: Record<any, any> = {};
keys.forEach((key, index) => {
const stringValue = values[index];
if (key in keyToTransformerLookup) {
rowObject[key] = keyToTransformerLookup[key](stringValue); // TypeScript complains
return;
}
rowObject[key] = stringValue;
});
return rowObject as T;
});
}
Type 'keyof T' cannot be used to index type 'KeyToTransformerLookup<T>'.
我猜这是因为一旦你映射 K in keyof T,生成的类型甚至不再是 keyof T 的部分类型,而是完全新的类型。这有点令人沮丧,因为我并没有转换这些键,而且我也在检查 key 是否存在于 keyToTransformerLookup,所以我只是想找到一种索引 keyToTransformerLookup 的方式,以满足 TypeScript。
答案#1
TypeScript 对通过 in 运算符进行类型缩小的支持(另见 TS4.9 对此的更新)目前只影响被检查的 对象 的表面类型,而不影响 键。也就是说,if (k in o) {⋯} else {⋯} 只对 o 有意义,而对 k 没有。目前在 microsoft/TypeScript#43284 上有一个开放的功能请求,建议也为 k 添加类型缩小支持,但截至 TS5.0,这还没有实现,并且实现起来可能会比较棘手,难以让所有人都满意。
所以当你检查 key in keyToTransformerLookup 时,key 的类型固执地保持为 keyof T,无法缩小为 keyof KeyToTransformerLookup<T>。
除非 TypeScript 添加对你尝试进行的那种类型缩小的支持,否则你将不得不想办法绕过它。 对于一次性的检查,最简单的解决方法是直接断言类型已经被缩小:
if (key in keyToTransformerLookup) {
rowObject[key] = keyToTransformerLookup[
key as keyof KeyToTransformerLookup<T>
](stringValue);
return;
}
如果你发现自己经常进行这种检查,你可以为它编写一个自定义类型保护函数:
function isKeyof<T extends object>(
k: PropertyKey, o: T): k is keyof T {
return k in o;
}
然后使用它:
if (isKeyof(key, keyToTransformerLookup)) {
rowObject[key] = keyToTransformerLookup[key](stringValue);
return;
}
请注意,从技术上来说进行这种缩小类型是不安全的。TypeScript 中的对象类型是开放的或可扩展的,而不是封闭的或“精确”的(如 microsoft/TypeScript#12936 所讨论的)。对象总是可以拥有比其类型描述更多的属性。这意味着可能发生如下情况:
interface Foo {
x: string;
y: number;
}
const xformer = {
y: (s: string) => +s,
x: "oopsiedoodle" // <-- this is an excess property
}
csvToArray<Foo>("...", ["x", "y"], xformer); // no compiler error, but:
// 💥 RUNTIME ERROR! keyToTransformerLookup[key] is not a function
这可能不太可能,但你应该了解它。请参阅 extend 并仅指定已知属性?以获取更多信息。
代码的 Playground 链接
