如何在函数中的记录中使用联合类型作为通用参数
How to use union type as a generic parameter in a record which in a function
我对 Record 中的通用联合类型感到困惑。
这是我的代码(TypeScript 版本:4.6.3):
const fn = <T extends number | string = string>() => {
const map: Record<T, string> = {};
};
我希望它能起作用,但编辑器显示 map:
错误
'map' is declared but its value is never read.ts(6133);
Type '{}' is not assignable to type 'Record<T, string>'.ts(2322)
当我这样写的时候,它起作用了:
type GetRecord<T extends string | number> = Record<T, string>;
type AAAA = GetRecord<string | number>;
const map: AAAA = {};
那么,如果我想在函数中的 record which 中使用联合类型作为泛型参数,我该怎么办?
而且,如果我这样写:
const fn = <T extends number | string = string>() => {
const map: Record<T, string> = {} as Record<T, string>;
const data: Record<string, string> = {};
Object.keys(data).forEach((key) => {
map[key] = data[key];
});
};
显示错误:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Record<T, string>'.
No index signature with a parameter of type 'string' was found on type 'Record<T, string>'.
我不明白,T是extends string或number,key是string类型。为什么我不能将键设置为要映射的对象键?
因为extends
检查左轴是否可以分配给右轴。让我们使用 extends
编写一些测试以更好地了解其行为方式:
type T1 = {} extends Record<number | string, string> ? true : false;
// ^? true
type T2 = {} extends Record<string, string> ? true : false;
// ^? true
type T3 = {} extends Record<number, string> ? true : false;
// ^? true
所有这些都按预期工作,但请注意当我们使用文字代替时:
type T4 = {} extends Record<1 | 2 | 3, string> ? true : false;
// ^? false
不行!为什么?
嗯,Record<1 | 2 | 3, string>
实际上是:
{
1: string;
2: string;
3: string;
}
现在您看到 {}
不可分配给此类型。但是,我们为什么要首先尝试查看文字是否可行?
这些文字 (1, 2, 3) 是更一般的 number
类型的 子类型 。 string
.
类型的文字“foo”、“bar”和“baz”也是如此
代码中的赋值不起作用,因为 T
可以是 任何字符串或数字类型,包括文字 。换句话说,TypeScript 不确定 T
不能包含文字类型。因此,TypeScript 不允许您将 {}
分配给 Record<T, string>
.
你可以通过强制转换来解决这个问题:
const map = {} as Record<T, string>;
此 playground 通过强制转换演示了上述测试和变通解决方案。
(根据问题编辑更新)
让我们尝试从 TypeScript 解密此错误消息:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Record<T, string>'.
No index signature with a parameter of type 'string' was found on type 'Record<T, string>'.
确实很奇怪。直到你考虑并记住上面的测试。我们看到文字不起作用,因为它们太具体了。
所以让我们用 extends
编写更多测试:
type T6 = 1 | 2 | 3 extends number ? true : false;
// ^? true
type T7 = "a" | "b" extends string ? true : false;
// ^? true
type T8 = number extends 1 | 2 | 3 ? true : false;
// ^? false
type T9 = string extends "a" | "b" ? true : false;
// ^? false
现在很明显,文字可以分配给它们各自更通用的父类型,但反之则不行。这是直觉和预期的。您不能给期望 1、2 或 3 的任何数字,也不能将任何字符串仅分配给“a”或“b”。
那么这告诉我们什么?
提示在错误信息的最后一行:
No index signature with a parameter of type 'string' was found on type 'Record<T, string>'.
回想一下 T
可以是 string | number
的任何扩展。这包括 1 | 2 | 3
、"a" | "b"
甚至 1 | 2 | 3 | "a" | "b"
.
现在很清楚,因为 T
可能比普通 string
更具体,你不能使用普通 string
索引到 Record<T, string>
。
但是,如果您确定密钥可以与 T
一起使用,还可以使用另一个转换:
map[key as T] = data[key];
另一个 playground 演示了我们刚刚发现的内容。
我对 Record 中的通用联合类型感到困惑。
这是我的代码(TypeScript 版本:4.6.3):
const fn = <T extends number | string = string>() => {
const map: Record<T, string> = {};
};
我希望它能起作用,但编辑器显示 map:
错误'map' is declared but its value is never read.ts(6133);
Type '{}' is not assignable to type 'Record<T, string>'.ts(2322)
当我这样写的时候,它起作用了:
type GetRecord<T extends string | number> = Record<T, string>;
type AAAA = GetRecord<string | number>;
const map: AAAA = {};
那么,如果我想在函数中的 record which 中使用联合类型作为泛型参数,我该怎么办?
而且,如果我这样写:
const fn = <T extends number | string = string>() => {
const map: Record<T, string> = {} as Record<T, string>;
const data: Record<string, string> = {};
Object.keys(data).forEach((key) => {
map[key] = data[key];
});
};
显示错误:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Record<T, string>'.
No index signature with a parameter of type 'string' was found on type 'Record<T, string>'.
我不明白,T是extends string或number,key是string类型。为什么我不能将键设置为要映射的对象键?
因为extends
检查左轴是否可以分配给右轴。让我们使用 extends
编写一些测试以更好地了解其行为方式:
type T1 = {} extends Record<number | string, string> ? true : false;
// ^? true
type T2 = {} extends Record<string, string> ? true : false;
// ^? true
type T3 = {} extends Record<number, string> ? true : false;
// ^? true
所有这些都按预期工作,但请注意当我们使用文字代替时:
type T4 = {} extends Record<1 | 2 | 3, string> ? true : false;
// ^? false
不行!为什么?
嗯,Record<1 | 2 | 3, string>
实际上是:
{
1: string;
2: string;
3: string;
}
现在您看到 {}
不可分配给此类型。但是,我们为什么要首先尝试查看文字是否可行?
这些文字 (1, 2, 3) 是更一般的 number
类型的 子类型 。 string
.
代码中的赋值不起作用,因为 T
可以是 任何字符串或数字类型,包括文字 。换句话说,TypeScript 不确定 T
不能包含文字类型。因此,TypeScript 不允许您将 {}
分配给 Record<T, string>
.
你可以通过强制转换来解决这个问题:
const map = {} as Record<T, string>;
此 playground 通过强制转换演示了上述测试和变通解决方案。
(根据问题编辑更新)
让我们尝试从 TypeScript 解密此错误消息:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Record<T, string>'.
No index signature with a parameter of type 'string' was found on type 'Record<T, string>'.
确实很奇怪。直到你考虑并记住上面的测试。我们看到文字不起作用,因为它们太具体了。
所以让我们用 extends
编写更多测试:
type T6 = 1 | 2 | 3 extends number ? true : false;
// ^? true
type T7 = "a" | "b" extends string ? true : false;
// ^? true
type T8 = number extends 1 | 2 | 3 ? true : false;
// ^? false
type T9 = string extends "a" | "b" ? true : false;
// ^? false
现在很明显,文字可以分配给它们各自更通用的父类型,但反之则不行。这是直觉和预期的。您不能给期望 1、2 或 3 的任何数字,也不能将任何字符串仅分配给“a”或“b”。
那么这告诉我们什么?
提示在错误信息的最后一行:
No index signature with a parameter of type 'string' was found on type 'Record<T, string>'.
回想一下 T
可以是 string | number
的任何扩展。这包括 1 | 2 | 3
、"a" | "b"
甚至 1 | 2 | 3 | "a" | "b"
.
现在很清楚,因为 T
可能比普通 string
更具体,你不能使用普通 string
索引到 Record<T, string>
。
但是,如果您确定密钥可以与 T
一起使用,还可以使用另一个转换:
map[key as T] = data[key];
另一个 playground 演示了我们刚刚发现的内容。