这是一个有趣的问题。对于条件类型中的泛型类型参数,Typescript通常不能做太多事情。如果发现对extends
的评估涉及类型参数,它只会推迟对它的评估。
如果我们可以让打字稿使用一种特殊的类型关系,即平等关系(而不是扩展关系),则适用例外。对于编译器而言,相等关系很容易理解,因此无需推迟条件类型评估。通用约束是编译器中使用类型相等的少数几个位置之一。让我们看一个例子:
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,"value">;
// other: "other";
// action: never;
// }
}
Playground Link
以上类型KeysOfIdenticalType
与我们过滤所需的类型很接近。对于other
,保留属性名称。对于action
,属性名称被删除。 value
周围只有一个令人讨厌的问题。由于value
的类型为T
,因此无法轻易解决T
和(v: T) => void
不相同(实际上可能不相同)的问题。
我们仍然可以确定value
与T
相同:对于类型T
的属性,将(v: T) => void
的此检查与never
相交。与never
的任何交集都可以简单地解析为never
。然后,我们可以使用另一种身份检查来添加类型为T
的属性:
interface Model<T> {
value: T,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],K> & Identical<M[K],T,K>) // Identical<M[K],K> will be never is the type is T and this whole line will evaluate to never
| Identical<M[K],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>,P> & Identical<Model[P],G,P>)
| Identical<Model[P],P,never>
}[keyof Model]>;
// My utility function.
type Action<Model extends object,G = unknown> = (data: State<Model,G>) => State<Model,G>;
type Identical<T,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,doSomething: data => {
data.value; // Ok
data.str
data.doSomething; // Does not exist ?
return data;
}
};
}
Playground Link
注释:这里的限制是,它仅适用于一个类型参数(尽管可以将其改编为更多类型)。而且,该API对于任何使用者而言都有些混乱,因此这可能不是最佳解决方案。可能有一些我尚未发现的问题。如果有找到,让我知道?
,
如果我能说T不是Action类型的话,那将是很棒的。延伸的倒数
就像您说的那样,问题是我们还没有负面约束。我也希望他们能尽快获得这种功能。在等待时,我提出了这样的解决方法:
type KeysOfNonType<A extends object,B> = {
[K in keyof A]-?: A[K] extends B ? never : K
}[keyof A];
// CHANGE: use `Pick` instead of `Omit` here.
type State<Model extends object> = Pick<Model,KeysOfNonType<Model,Action<any>>>;
type Action<Model extends object> = (data: State<Model>) => State<Model>;
interface MyModel<T> {
value: T;
doSomething: Action<MyModel<T>>;
}
function modelFactory<T>(value: T): MyModel<T> {
return {
value,doSomething: data => {
data.value; // Now it does exist ?
data.doSomething; // Does not exist ?
return data;
}
} as MyModel<any>; // <-- Magic!
// since `T` has yet to be known
// it literally can be anything
}
,
count
和value
将始终使编译器不满意。要解决此问题,您可以尝试执行以下操作:
{
value,count: 1,transform: (data: Partial<Thing<T>>) => {
...
}
}
由于使用的是Partial
实用程序类型,因此在没有transform
方法的情况下您会没事的。
Stackblitz
,
通常,我读过两次,但并不完全了解您想要实现的目标。根据我的理解,您想从精确分配给interface Thing<T> {
value: T;
count: number;
transform: (data: Omit<Thing<T>,'transform'>) => void; // here the argument type is Thing without transform
}
// ? the factory function accepting the generic
function makeThing<T>(value: T): Thing<T> {
return {
value,transform: data => {
data.count; // exist
data.value; // exist
},};
}
的类型中省略@functions
。要做到这一点很简单,我们需要使用Omit:
@functions
{
async System.Threading.Tasks.Task RenderArea(Area area)
{
<tr>
<td class="@area.CssClass">
<p class="compact">
<a href="/Area/@area.Id" class="font-weight-bold">@area.Title</a>
@if (!string.IsNullOrWhiteSpace(area.Description))
{
<br />@area.Description
}
</p>
</td>
<td class="@area.CssClass">
<img src="~/images/Edit.png" class="edit-area button-img" data-id="@area.Id" title="Edit" />
<img src="~/images/Delete.png" class="delete-area button-img" data-id="@area.Id" title="Delete" />
</td>
</tr>
}
}
由于您在其他实用程序类型中提供了相当复杂的功能,因此不确定这是否是您想要的。希望对您有所帮助。
本文链接:https://www.f2er.com/3148874.html