I have types and the Save function below
我有类型和下面的保存功能
type Collection<K extends string, EntityType> = {
save(key: K, value: EntityType): void;
get(key: K, value: EntityType): EntityType[];
all(): EntityType[];
};
type Domain = {
[key: string]: Collection<any, any>;
};
function Save<D extends Domain, K extends string, V>(
dataStore: D,
key: K,
value: V
) {
if (key.startsWith("orders/")) {
dataStore.orders.save(key, value);
} else if (key.startsWith("customers/")) {
dataStore.customers.save(key, value);
}
//...
}
I want to use Save()
like below. It should automatically narrow the value parameter type in Save()
call to the corresponding entity type by looking up Collection<>
members in CommerceDomain
我想像下面这样使用Save()。它应该通过在CommerceDomain中查找Collection<>成员,自动将Save()调用中的值参数类型缩小到相应的实体类型
type CustomerId = `customers/${string}`;
type OrderId = `orders/${string}`;
type ProductId = `products/${string}`;
interface Customer { customer: string }
interface Order { order: string }
interface Product { product: string }
declare const customer: Customer;
declare const order: Order;
declare const product: Product;
type CommerceDomain = {
orders: Collection<OrderId, Order>;
customers: Collection<CustomerId, Customer>;
products: Collection<ProductId, Product>;
};
declare const store: CommerceDomain;
save(store, "orders/1", {/* should expect Order */});
save(store, "customers/1", {/* should expect Customer */});
更多回答
Does this approach meet your needs? Note that we can't assume that key
is a string
inside save
(or is it Save
)? since K
in Collection
isn't required to be a string
. Anyway, if that approach meets your needs I'll write up an answer explaining. If not, please edit the question to demonstrate unmet use cases, and while you're at it, make sure the code is a minimal reproducible example without problems you don't have (e.g., undeclared types/values, the key
thing, typos, etc). Let me know how to proceed. Thanks!
这种方法是否满足您的需求?注意,我们不能假设key是save中的字符串(或者是Save)?因为集合中的K不需要是字符串。无论如何,如果这种方法符合你的需要,我会写一个答案解释。如果没有,请编辑问题以演示未满足的用例,当你在做的时候,确保代码是一个最小的可重复的例子,没有你没有的问题(例如,未声明的类型/值,关键的东西,错别字等)。告诉我下一步怎么做。谢谢!
@jcalz We could actually assume it can be a string. tsplay.dev/m3V1AN Apparently it provides better inference in this way. Your example was a good starting point. Thank you. Please post it as an answer I will accept! Thank you.
@jcalz我们实际上可以假设它可以是一个字符串。Tsplay.dev/m3V1显然它以这种方式提供了更好的推论。你的例子是一个很好的起点。谢谢。请把它贴出来,作为我会接受的回答!谢谢。
One approach would be to make save()
generic only in D
, the type of the dataStore
parameter, and in K
, the type of the key
parameter, and then compute the type of the value
parameter from D
and K
. We will also want to constrain K
to be applicable to D
. To that end, let's create some utility types. DomainKey<D>
will be the constraint for K
and thus key
, and then DomainKeyValue<D, K>
will be the associated type for value
:
一种方法是只在D(数据存储参数的类型)和K(键参数的类型)中使save()成为泛型,然后从D和K计算Value参数的类型。我们还希望约束K以适用于D。为此,让我们创建一些实用程序类型。DomainKey
将是K的约束,因此Key,然后DomainKeyValue
将是Value的关联类型:
type DomainKey<D extends Domain> =
{ [P in keyof D]-?: D[P] extends Collection<infer K, any> ? K : never }[keyof D]
type DomainKeyValue<D extends Domain, K extends DomainKey<D>> =
{ [P in keyof D]-?: D[P] extends Collection<K, infer V> ? V : never }[keyof D]
These are both distributive object types (as coined in microsoft/TypeScript#47109. If you have a union of keylike types KU
= K1 | K2 | K3
and want to distribute a type function F<>
over it to get F<K1> | F<K2> | F<K3>
, you can do it like {[P in KU]: F<P>}[KU]
, which creates a mapped type and then immediately indexes into it.
这两种类型都是分布式对象类型(如微软/TypeScrip#47109中所创造的。如果您有一个类键类型的联合KU=K1|K2|K3,并且想要在其上分发类型函数F<>以获得F
|F
|F
,则可以像{[P in KU]:F
}[KU]那样创建一个映射类型,然后立即对其进行索引。
In our case our union of keylike types is keyof D
, and the type function we want to apply to each key P
of that union is D[P] extends Collection<infer K, any> ? K : never
in the case of DomainKey
and D[P] extends Collection<K, infer V> ? V : never
in the case of DomainKeyValue
. These are both using conditional type inference to extract the appropriate K
and V
type arguments out of Collection<K, V>
.
在我们的例子中,类键类型的联合是KeyOf D,并且我们想要应用于该联合的每个键P的类型函数是D[P]扩展集合<推断K,ANY>?K:在DomainKey和D[P]的情况下从不扩展集合
?V:在DomainKeyValue的情况下永远不会。它们都使用条件类型推理从集合
中提取适当的K和V类型参数。
Let's test those to make sure it works:
让我们测试一下,以确保它能正常工作:
type TestDomain = {
a: Collection<"foo", string>;
b: Collection<"bar", number>;
c: Collection<"baz", boolean>
}
type TestK = DomainKey<TestDomain>
// type TestK = "foo" | "bar" | "baz"
type TestV = DomainKeyValue<TestDomain, "bar">
// type TestV = number
Looks good. Conceptually save()
would therefore look like
看上去不错。因此,从概念上讲,save()如下所示
function save<D extends Domain, K extends DomainKey<D>>(
dataStore: D,
key: K,
value: DomainKeyValue<D, K>
) { /* impl */ }
And this does work, but unfortunately the IntelliSense only shows the utility type name DomainKey<CommerceDomain>
for the type of key
, which might not help the user:
这确实有效,但遗憾的是,IntelliSense仅显示密钥类型的实用程序类型名称DomainKey
,这可能对用户没有帮助:
// save(
// dataStore: CommerceDomain,
// key: DomainKey<CommerceDomain>, // 👎
// value: DomainKeyValue<CommerceDomain, DomainKey<CommerceDomain>>
// ): void
So we can use a trick to ask the compiler to expand it out, by intersecting K
with some specific supertype like string
so that the compiler decides to evaluate it fully, showing something more useful:
因此,我们可以使用一个技巧来要求编译器将其扩展,方法是将K与某个特定的超类型(如字符串)相交,以便编译器决定完全计算它,显示一些更有用的东西:
function save<D extends Domain, K extends DomainKey<D>>(
dataStore: D,
key: K & string, // 👈
value: DomainKeyValue<D, K>
) { /* impl */ }
// save(
// dataStore: CommerceDomain,
// key: `orders/${string}` | `customers/${string}` | `products/${string}`, // 👍
// value: DomainKeyValue<CommerceDomain, DomainKey<CommerceDomain>>
// ): void
Okay let's see if it works on the example from the question:
好的,让我们看看它对问题中的示例是否有效:
save(store, 'orders/1', order); // okay
save(store, 'customers/1', customer); // okay
save(store, 'customers/1', product); // error!
// ----------------------> ~~~~~~~
// 'Product' is not assignable to parameter of type 'Customer'.
save(store, 'products/1', product); // okay
Looks good! The inputs are constrained as desired.
看上去不错!根据需要对输入进行约束。
Playground link to code
Playground链接到代码
更多回答
That's an awesome answer! Thank you.
这是一个很棒的答案!谢谢。
我是一名优秀的程序员,十分优秀!