gpt4 book ai didi

typescript - 类型映射时通用属性的问题

转载 作者:行者123 更新时间:2023-12-03 15:20:21 26 4
gpt4 key购买 nike

我有一个库,它导出类似于以下的实用程序类型:

type Action<Model extends object> = (data: State<Model>) => State<Model>;

此实用程序类型允许您声明将作为“操作”执行的函数。它接收一个通用参数是 Model行动将针对。
data然后使用我导出的另一种实用程序类型输入“ Action ”的参数;

type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;
State实用型基本上取传入 Model generic 然后创建一个新类型,其中所有属于 Action 类型的属性。已被删除。

例如这是上述的基本用户土地实现;

interface MyModel {
counter: number;
increment: Action<Model>;
}

const myModel = {
counter: 0,
increment: (data) => {
data.counter; // Exists and typed as `number`
data.increment; // Does not exist, as stripped off by State utility
return data;
}
}

上述工作非常好。 👍

但是,有一种情况我很挣扎,特别是在定义了泛型模型定义以及用于生成泛型模型实例的工厂函数时。

例如;

interface MyModel<T> {
value: T; // 👈 a generic property
doSomething: Action<MyModel<T>>;
}

function modelFactory<T>(value: T): MyModel<T> {
return {
value,
doSomething: data => {
data.value; // Does not exist 😭
data.doSomething; // Does not exist 👍
return data;
}
};
}

在上面的例子中,我期望 data要在 doSomething 处输入的参数操作已被删除,通用 value属性(property)仍然存在。然而事实并非如此 - value我们的 State 也删除了属性(property)公用事业。

我相信造成这种情况的原因是 T是泛型的,没有对其应用任何类型限制/缩小,因此类型系统决定它与 Action 相交。键入并随后将其从 data 中删除参数类型。

有没有办法绕过这个限制?我做了一些研究,希望有某种机制可以说明 T是除 Action 之外的任何一种.即否定类型限制。

想象:

function modelFactory<T extends any except Action<any>>(value: T): UserDefinedModel<T> {

但是 TypeScript 不存在该功能。

有谁知道我可以按照我的预期让它工作的方法吗?

为了帮助调试,这里有一个完整的代码片段:

// Returns the keys of an object that match the given type(s)
type KeysOfType<A extends object, B> = {
[K in keyof A]-?: A[K] extends B ? K : never
}[keyof A];

// Filters out an object, removing any key/values that are of Action<any> type
type State<Model extends object> = Omit<Model, KeysOfType<Model, Action<any>>>;

// My utility function.
type Action<Model extends object> = (data: State<Model>) => State<Model>;

interface MyModel<T> {
value: T; // 👈 a generic property
doSomething: Action<MyModel<T>>;
}

function modelFactory<T>(value: T): MyModel<T> {
return {
value,
doSomething: data => {
data.value; // Does not exist 😭
data.doSomething; // Does not exist 👍
return data;
}
};
}

您可以在此处使用此代码示例:
https://codesandbox.io/s/reverent-star-m4sdb?fontsize=14

最佳答案

这是一个有趣的问题。对于条件类型中的泛型类型参数,Typescript 通常不能做太多事情。它只是推迟对 extends 的任何评估如果它发现评估涉及一个类型参数。

如果我们可以让 typescript 使用一种特殊的类型关系,即 ,则异常(exception)情况适用。相等关系 (不是扩展关系)。对于编译器来说,相等关系很容易理解,因此不需要推迟条件类型评估。泛型约束是编译器中为数不多的使用类型相等的地方之一。让我们看一个例子:

function m<T, K>() {
type Bad = T extends T ? "YES" : "NO" // unresolvable in ts, still T extends T ? "YES" : "NO"

// Generic type constrains are compared using type equality, so this can be resolved inside the function
type Good = (<U extends T>() => U) extends (<U extends T>() => U) ? "YES" : "NO" // "YES"

// If the types are not equal it is still un-resolvable, as K may still be the same as T
type Meh = (<U extends T>()=> U) extends (<U extends K>()=> U) ? "YES": "NO"
}

Playground Link

我们可以利用这种行为来识别特定类型。现在,这将是精确类型匹配,而不是扩展匹配,并且精确类型匹配并不总是合适的。但是,由于 Action只是一个函数签名,精确的类型匹配可能足够好。

让我们看看我们是否可以提取匹配更简单的函数签名的类型,例如 (v: T) => void :
interface Model<T> {
value: T,
other: string
action: (v: T) => void
}

type Identical<T, TTest, TTrue, TFalse> =
((<U extends T>(o: U) => void) extends (<U extends TTest>(o: U) => void) ? TTrue : TFalse);

function m<T>() {
type M = Model<T>
type KeysOfIdenticalType = {
[K in keyof M]: Identical<M[K], (v: T) => void, never, K>
}
// Resolved to
// type KeysOfIdenticalType = {
// value: Identical<T, (v: T) => void, never, "value">;
// other: "other";
// action: never;
// }

}

Playground Link

以上类型 KeysOfIdenticalType接近我们过滤所需的内容。对于 other ,属性名称被保留。对于 action ,属性名称被删除。 value周围只有一个讨厌的问题.自 value类型为 T , 不是很容易解决的 T , 和 (v: T) => void不相同(实际上它们可能不相同)。

我们仍然可以确定 valueT 相同:对于 T 类型的属性,与此检查相交 (v: T) => voidnever .与 never 的任何交集可以简单地解析为 never .然后我们可以添加回 T 类型的属性。使用另一个身份检查:
interface Model<T> {
value: T,
other: string
action: (v: T) => void
}

type Identical<T, TTest, TTrue, TFalse> =
((<U extends T>(o: U) => void) extends (<U extends TTest>(o: U) => void) ? TTrue : TFalse);

function m<T>() {
type M = Model<T>
type KeysOfIdenticalType = {
[K in keyof M]:
(Identical<M[K], (v: T) => void, never, K> & Identical<M[K], T, never, K>) // Identical<M[K], T, never, K> will be never is the type is T and this whole line will evaluate to never
| Identical<M[K], T, K, never> // add back any properties of type T
}
// Resolved to
// type KeysOfIdenticalType = {
// value: "value";
// other: "other";
// action: never;
// }

}

Playground Link

最终的解决方案如下所示:
// Filters out an object, removing any key/values that are of Action<any> type
type State<Model extends object, G = unknown> = Pick<Model, {
[P in keyof Model]:
(Identical<Model[P], Action<Model, G>, never, P> & Identical<Model[P], G, never, P>)
| Identical<Model[P], G, P, never>
}[keyof Model]>;

// My utility function.
type Action<Model extends object, G = unknown> = (data: State<Model, G>) => State<Model, G>;


type Identical<T, TTest, TTrue, TFalse> =
((<U extends T>(o: U) => void) extends (<U extends TTest>(o: U) => void) ? TTrue : TFalse);

interface MyModel<T> {
value: T; // 👈 a generic property
str: string;
doSomething: Action<MyModel<T>, T>;
method() : void
}


function modelFactory<T>(value: T): MyModel<T> {
return {
value,
str: "",
method() {

},
doSomething: data => {
data.value; // ok
data.str //ok
data.method() // ok
data.doSomething; // Does not exist 👍
return data;
}
};
}

/// Still works for simple types
interface MyModelSimple {
value: string;
str: string;
doSomething: Action<MyModelSimple>;
}


function modelFactory2(value: string): MyModelSimple {
return {
value,
str: "",
doSomething: data => {
data.value; // Ok
data.str
data.doSomething; // Does not exist 👍
return data;
}
};
}

Playground Link

备注:这里的限制是这只适用于一种类型参数(尽管它可能适用于更多)。此外,API 对任何消费者来说都有些困惑,因此这可能不是最佳解决方案。可能存在我尚未确定的问题。如果你找到了,请告诉我😊

关于typescript - 类型映射时通用属性的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58738700/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com