我有以下类型和一个枚举:
enum TableNames {
clients = "clients",
products = "products"
}
interface IClient {
client: string;
location: string;
type: string;
}
interface IProduct {
product: string;
family: string;
subfamily: string;
}
type TSelector<T> = {
[K in keyof T]: T[K][];
};
type TClientSelector = TSelector<IClient>;
type TProductSelector = TSelector<IProduct>;
这两个类基于TClientSelector
和TProductSelector
初始化一个对象。
export class BaseClientSelector implements TClientSelector {
client: string[] = [];
type: string[] = [];
location: string[] = [];
}
export class BaseProductSelector implements TProductSelector {
product: string[] = [];
family: string[] = [];
subfamily: string[] = [];
}
使用变量name
分派类型为TClientSelector
或TProductSelector
的对象的工厂函数。
const getEmptySelectorObject = (name: TableNames): TClientSelector | TProductSelector => {
if (name === TableNames.clients) {
return new BaseClientSelector();
} else {
return new BaseProductSelector();
}
};
问题就在这里。考虑以下函数:
export const getSelectorData = (
arrayOfObj: (IClient | IProduct)[],
name: TableNames
): TClientSelector | TProductSelector => {
const selectorData: TClientSelector | TProductSelector = getEmptySelectorObject(name);
for (const col in selectorData) {
selectorData[col] = Array.from(
// ^^^^^^^^^^^^^^^^^ TS7053
new Set(arrayOfObj.map((obj: IClient | IProduct) => obj[col]))
// ^^^^^^^^ TS7053
).sort((a, b) => String(a).localeCompare(String(b)));
}
return selectorData;
};
如果name
的类型是TableNames.clients
,那么selectorData
的类型必须是TClientSelector
。但是,由于TypeScript无法知道selectorData
将具有哪个类型,因为我不知道如何向它表达这种关系,因此当迭代selectorData
的键时,它默认将col
分配给类型string
。这提示编译器告诉我TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'TSelector<IClient> | TSelector<IProduct>'. No index signature with a parameter of type 'string' was found on type 'TSelector<IClient> | TSelector<IProduct>'
。
类似的情况发生在Map中,obj
可以是IClient
或IProduct
,所以col
不能用于索引它。
我尝试过的解决方案无法说服我:
- 带类型保护的代码重复。
- 正在添加显式
any
% s。 - 函数重载(一个用于
TClientSelector
,另一个用于TProductSelector
。
我相当肯定这可以使用泛型来解决,但我无法理解它们。
我找到了这个答案的jcalz https://stackoverflow.com/a/72294288,它使用了一个 * 分布式对象类型 *,有些东西告诉我这可能是答案,但我不能适应这个解决方案我的问题。
1条答案
按热度按时间zf2sa74q1#
现在,甚至
getSelectorData()
的调用端也是一个问题,因为两个参数之间的相关性没有表示出来。您的呼叫签名如下所示:所以它允许这样:
您可以使用REST参数元组的区分并集来强制这种关联:
但这只会从呼叫者的一方帮助。
实现仍然有错误,这是因为TypeScript不太支持“相关的联合”类型,在这种情况下,对于某个联合类型表达式的每一个可能的缩小,需要分析一次单个代码块,如microsoft/TypeScript#30581中所讨论的。
在你的函数实现中,你会希望编译器注意到,如果函数参数的类型是
[IClient[], TableNames.clients]
,那么一切都很好,如果它们的类型是[IProduct[], TableNames.products]
,那么它们也很好,所以它们总体上都很好。但是TypeScript并不做任何类型的“分布式”控制流分析(甚至不像microsoft/TypeScript#25051中建议的那样在“选择加入”的基础上)。相反,编译器将每个union类型的值视为独立的,因此即使调用签名阻止传入[IClient[], TableNames.products]
,编译器在实现中也没有意识到这一点。所以它抱怨。官方推荐的修复此类问题的方法在microsoft/TypeScript#47109中描述。这个想法是从联合切换到[泛型] https://www.typescriptlang.org/docs/handbook/2/generics.html),这些泛型被约束到一些基本接口的键。然后你必须重写所有的类型和操作,在这个基本接口上重写mapped types,在这个接口上重写泛型indexes。
但这样做通常是一个重大的重构,可能很难遵循。如果您想要方便,您应该在仔细检查您的实现是否良好之后Assert或使用
any
类型。治疗可能比疾病更糟糕。你是法官:
在你的情况下,我会重构如下。下面是表示您想要抽象的关系的基本接口:
现在,您可以将
TSelector
重写为泛型,覆盖TableMap
的 * key *,如下所示:实用程序类型
ArrayProps<T>
只是让你很容易表示你想要的TSelector<K>
。请注意,TSelector<K>
使用其所有键索引到Map类型,这使其成为ms/TS#47109中创造的 * 分布式对象类型 *。我将TSelectorMap<K>
命名为Map类型部分,因为它在后面会很有用。您的
TClientSelector
和TProductSelector
类型可以稍微重写为:你可以验证它们和以前一样。
现在,为了使其工作,您的
getEmptySelectorObject()
函数必须以相同的方式泛型。如果它返回一个联盟,那么我们将陷入困境。由于返回类型将是TSelector<K>
,一个指向TSelectorMap<K>
的索引,其键类型为K
,那么说服编译器这样做的方法是实际创建一个TSelectorMap<K>
类型的值并索引到它中。下面是一个TSelectorMap<TableNames>
(任何K extends TableNames
的TSelectorMap<K>
子类型):请注意,我使用的是getter methods,因此实际代码不会运行,直到有人尝试访问该属性。现在
getEmptySelectorObject()
函数实现只需要在baseSelectors
中查找name
:差不多吧编译器无法“看到”
baseSelectors[name]
是TSelectorMap<K>[K]
类型。相反,它只“看到”TSelectorMap<TableNames>[K]
,并抱怨。但是它确实知道TSelectorMap<TableNames>
是TSelectorMap<K>
的子类型,所以我们可以通过注解一个更宽类型的新变量并赋值来安全地“向上转换”。最后,
getSelectorData()
:它已经变得通用了,而且很有效。还有一点类型的安全孔你仍然可以调用
getSelectorData()
,其中K
是完整的TableNames
联合体,for
循环不会检查你是否将错误的属性分配给错误的列(col
或多或少只是keyof TSelector<K>
,但你真的需要一个通用的P extends keyof TSelector<K>
)。但这是一阶重构。所以,这对你来说值得吗?我不能说,但我更愿意维护我个人理解的代码,而不是Stack Overflow中的某个人给我的一堆GitHub问题的链接。
Playground链接到代码