首页 > 解决方案 > 为什么这个映射类型会移除 `?` 装饰器?我们如何在不删除它的情况下获得类似的结果?

问题描述

问题

我们正在构建一个不包括 type 属性的映射类型Function。我们的方法有一个问题:它还?从映射属性中删除了可选的 ( ) 装饰器。

再生产

这是该行为的简化再现NoOpMap1行为如我们所愿,并且NoOpMap2有问题的行为。

type NoOpMap1<T> = { // Good. This one does not remove the ?
    [K in keyof T]: T[K];
};

type Keys<T> = {
    [K in keyof T]: K;
}[keyof T];

type NoOpMap2<T> = { // Problem. This one removes the ?
    [K in Keys<T>]: T[K];
};

演示

type SomeType = {
    foo?: string,
}

// type SomeTypeNoOpMap1 = { foo?: string; }
type SomeTypeNoOpMap1 = NoOpMap1<SomeType>;

// type SomeTypeNoOpMap2 = { foo: string; }
type SomeTypeNoOpMap2 = NoOpMap2<SomeType>;

NoOpMap1表现如预期。?它将装饰器保留在foo物业上。NoOpMap2删除它。

问题

为什么要NoOpMap2删除?装饰器?我们如何在不删除它的情况下获得类似的结果?

实际用例

这是我们正在尝试构建的完整类型:

type DataPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

type DataPropertiesOnly<T> = {
  [K in DataPropertyNames<T>]
  : T[K] extends (string | number | boolean) ? T[K]
  : T[K] extends (infer A)[] ? DataPropertiesOnly<A>[]
  : DataPropertiesOnly<T[K]>;
};

如前所述,上述类型负责删除类型的属性,Function 而不?从剩余的属性中删除装饰器

标签: typescripttypesmapped-types

解决方案


如果要保留映射类型中属性的可选/只读状态,则需要确保编译器将映射视为同态。我知道有两种方法可以做到这一点。

一种是映射的形式{[K in keyof T]: ...}是您直接映射keyof Tsome T,通用或具体的形式。你必须有类似直接in keyof出现在类型中的东西,否则它不会被计算在内。

interface Foo {
    optional?: string;
    readonly viewonly: string;
}

type Homomorphic = { [K in keyof Foo]: 0 };
// type Homomorphic = { 
//   optional?: 0 | undefined; 
//   readonly viewonly: 0; 
// }

type KeyOf<T> = keyof T
type NonHomomorphic = { [K in KeyOf<Foo>]: 0 };
// type NonHomomorphic = { 
//   optional: 0; 
//   viewonly: 0; 
// }

另一种方法是映射一个泛型类型参数,K该类型参数已被限制另一个泛型类型参数。所以:keyof T T

type GenericConstraint<T, K extends keyof T> = { [P in K]: 0 };
type ConstrainedHomomorphic = GenericConstraint<Foo, keyof Foo>;
// type ConstrainedHomomorphic = { 
//   optional?: 0 | undefined; 
//   readonly viewonly: 0; 
// }

type OnlySomeKeysStillHomomorphic = GenericConstraint<Foo, "viewonly">;
// type OnlySomeKeysStillHomomorphic = {
//   readonly viewonly: 0;
// }

后一种方法是专门添加的,以获取部分映射类型,例如Pick<T, K>同态。正是这种方法让您的实际用例发挥作用:

// unchanged
type DataPropertyNames<T> = {
    [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

// quick abort if T is a function or primitive
// otherwise pass to a homomorphic helper type 
type DataPropertiesOnly<T> =
    T extends Function ? never :
    T extends object ? DPO<T, DataPropertyNames<T>> :
    T

// homomorphic helper type
type DPO<T, KT extends keyof T> = {
    [K in KT]
    : T[K] extends (string | number | boolean) ? T[K]
    : T[K] extends (infer A)[] ? DataPropertiesOnly<A>[]
    : DataPropertiesOnly<T[K]>;
}

我认为这将按照您想要的方式进行。好的,希望有帮助;祝你好运!


推荐阅读