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

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

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

此实用程序类型允许您声明一个将作为“动作”执行的功能。它收到一个通用参数,即操作将针对的Model

然后使用导出的其他实用程序类型键入“操作”的data参数;

type State<Model extends object> = Omit<Model,KeysOfType<Model,action<any>>>;

State实用程序类型基本上采用传入的Model泛型,然后创建一个新类型,其中所有类型为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属性仍然存在。但是,情况并非如此-value工具的State属性也已删除。

我认为,原因是T是通用的,没有对其施加任何类型限制/缩小,因此类型系统确定它与action类型相交并随后将其删除来自data参数类型。

有没有办法解决这个限制?我已经进行了一些研究,希望能有某种机制可以说明Taction的任何 except 。即否定类型限制。

想象:

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,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

zhangziqun31 回答:类型映射时的通用属性问题

这是一个有趣的问题。对于条件类型中的泛型类型参数,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不相同(实际上可能不相同)的问题。

我们仍然可以确定valueT相同:对于类型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
}
,

countvalue将始终使编译器不满意。要解决此问题,您可以尝试执行以下操作:

{
  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

大家都在问