当前位置: 首页 > 知识库问答 >
问题:

使用泛型和合并对象的打字声明文件

邵凯定
2023-03-14

我创建了一个配置对象检查器函数,它基本上检查对象是否与蓝图匹配。与React道具类型的工作原理非常相似。我将使用它在不同的网站上使用不同的配置文件自动部署应用程序,以确保在尝试部署之前正确定义配置文件。

我有一个函数,它接受一个对象并返回一个函数。

const blueprint = {
stringValue: ConfigTypes.string, 
requirednumberValue: ConfigTypes.number.isRequired, 
boolOrStringValue: ConfigTypes.oneOfType([ConfigTypes.string, ConfigType.bool])
} //The syntax here is very similar to that of React Prop Types. I am essentially defining what I expect my object to look like.

const checker = ConfigChecker(blueprint)

ConfigChecker获取一个蓝图,说明我们期望对象中的键是什么,指定键的值类型是什么,以及它们是可选的还是必需的。ConfigChecker返回一个函数,该函数以两个对象为参数。

const config = {
stringValue: "Hello"
boolOrStringValue: true
}
const defaults = {
requirednumberValue: 5,
boolOrStringValue: false
}
checker(config, defaults) //checker is defined in the above example. The return of ConfigChecker(blueprint)

config参数是我们计划用于应用的配置对象,而默认值参数是我们可以用于应用的默认键值对,如果它们没有在配置对象中指定。

在内部,config参数和defaults参数与覆盖defaults对象中相同键值的配置对象深度合并在一起。

因此,上述示例的结果将是:

{
stringValue: "Hello"
boolOrStringValue: true
requirednumberValue: 5,
}

合并两个参数后,将根据blueprint对它们进行测试,以确保正确定义包含合并defaultconfig对象的最终配置对象。

此函数的声明文件如下所示:

interface Requireable {}
interface ConfigType {
  isRequired: Requireable;
}

type CType = {
  /**
   * A string value.
   */
  string: ConfigType;
  /**
   * A boolean value.
   */
  bool: ConfigType;
  /**
   * A number value.
   */
  number: ConfigType;
  /**
   * A function.
   */
  func: ConfigType;
  /**
   * An object. Not an array.
   */
  object: ConfigType;
  /**
   * An array.
   */
  array: ConfigType;
  /**
   * Any value.
   */
  any: ConfigType;
  /**
   * An array of a specific type.
   */
  arrayOf: (type: CType[keyof CType]) => ConfigType;
  /**
   * An object containing a specific type.
   */
  objectOfType: (type: CType[keyof CType]) => ConfigType;
  /**
   * One of these values.
   * @example
   * OneOf(["Hello", "Goodbye", false])
   */
  oneOf: (enums: Array<any>) => ConfigType;
  /**
   * One of these types.
   * @example
   * OneOf([ConfigType.string, ConfigType.number])
   */
  oneOfType: (types: Array<CType[keyof CType]>) => ConfigType;
  /**
   * An object with specific keys and value types.
   */
  objectOf: (obj: { [key: string]: CType[keyof CType] }) => ConfigType;
  /**
   * An object with specific keys and value types. The objects must strictly match.
   */
  exactObjectOf: (obj: { [key: string]: CType[keyof CType] }) => ConfigType;
};

/**
 * Check functions.
 */
export const ConfigTypes: CType;

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

type SpreadProperties<L, R, K extends keyof L & keyof R> = {
  [P in K]: L[P] | Exclude<R[P], undefined>;
};

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

type Spread<L, R> = Id<
  // Properties in L that don't exist in R
  Pick<L, Exclude<keyof L, keyof R>> &
    // Properties in R with types that exclude undefined
    Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>> &
    // Properties in R, with types that include undefined, that don't exist in L
    Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>> &
    // Properties in R, with types that include undefined, that exist in L
    SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
>;

export default function <S extends { [key: string]: CType[keyof CType] }>(
  schema: S
): <C extends { [key: string]: any }, D extends { [key: string]: any }>(
  config: C,
  defaults?: D
) => Pick<Spread<C, D>, keyof S>;

本质上,这个声明文件声明的是调用ConfigChecker(蓝图)(配置,默认值)的结果值应该是一个对象,该对象仅包含蓝图对象中的键,这些键的值类型来自合并的配置默认值对象。因此,如果我要向config对象添加一个键,而不首先定义蓝图对象中的键,返回的对象将不包含添加的键;只有蓝图对象中的键将在返回的对象中定义。

虽然这很有效,但它只是我真正想做的一个变通方法。

我真正想从我的申报文件中实现的是:

给定上面的蓝图,typecript应该推断返回的对象,在提供config默认值对象之前,应该是这样的:

{
stringValue?: string, 
requirednumberValue: number, 
boolOrStringValue?: boolean | string
}

然后,在添加了configdefaults对象之后,typescript应该将它们交叉引用到蓝图,并返回如下内容:

{
stringValue: string, 
requirednumberValue: number, 
boolOrStringValue: boolean
}

所以首先,TypeScript应该推断函数的返回类型应该是一个包含可选和必需键/值对的对象。这些键/值对可以有多种类型,就像:boolOrStringValue?: boolean|string一样,它既是可选的,也可以是布尔值或字符串。

最后,TypeScript应该读取合并的config默认值对象中的值,并替换已知类型的假设类型。

我知道第一个解决办法很好,但是,除了这是一个很酷的功能外,它还允许我在打字脚本上绞尽脑汁,因为我才刚刚开始。我喜欢做这样的事,把自己弄得筋疲力尽。像这样的事情可以做吗?或者我是在我的头上?

提前谢谢。

共有1个答案

窦夜洛
2023-03-14

我设法找到了自己的解决办法。这是新的声明文件:

interface OConfigType<T> {
  isRequired: RConfigType<T>;
}

interface RConfigType<_T> {
  (): void;
}

type GetType<T> = T extends OConfigType<infer T> | RConfigType<infer T>
  ? T
  : never;

type CType = {
  /**
   * A string value.
   */
  string: OConfigType<string>;
  /**
   * A boolean value.
   */
  bool: OConfigType<boolean>;
  /**
   * A number value.
   */
  number: OConfigType<number>;
  /**
   * A function.
   */
  func: OConfigType<Function>;
  /**
   * An object. Not an array.
   */
  object: OConfigType<Object>;
  /**
   * An array.
   */
  array: OConfigType<Array<any>>;
  /**
   * Any value.
   */
  any: OConfigType<any>;
  /**
   * An array of a specific type.
   */
  arrayOf: <S>(type: S) => OConfigType<Array<GetType<S>>>;
  /**
   * An object containing a specific type.
   */
  objectOfType: <S>(
    type: S
  ) => OConfigType<Record<string | number, GetType<S>>>;
  /**
   * One of these values.
   * @example
   * OneOf(["Hello", "Goodbye", false])
   */
  oneOf: <T>(
    enums: [...T]
  ) => T extends Array<infer U> ? OConfigType<U> : never;
  /**
   * One of these types.
   * @example
   * OneOf([ConfigType.string, ConfigType.number])
   */
  oneOfType: <T>(types: [...T]) => T extends Array<infer U> ? U : never;
  /**
   * An object with specific keys and value types.
   */
  objectOf: <S extends { [key: string | number]: OConfigType | RConfigType }>(
    obj: S
  ) => OConfigType<ReturnSchema<S>>;
  /**
   * An object with specific keys and value types. The objects must strictly match.
   */
  exactObjectOf: <
    S extends { [key: string | number]: OConfigType | RConfigType }
  >(
    obj: S
  ) => OConfigType<ReturnSchema<S>>;
};

/**
 * Check functions.
 */
export const ConfigTypes: CType;

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

type SpreadProperties<L, R, K extends keyof L & keyof R> = {
  [P in K]: L[P] | Exclude<R[P], undefined>;
};

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

type Spread<L, R> = Id<
  // Properties in L that don't exist in R
  Pick<L, Exclude<keyof L, keyof R>> &
    // Properties in R with types that exclude undefined
    Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>> &
    // Properties in R, with types that include undefined, that don't exist in L
    Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>> &
    // Properties in R, with types that include undefined, that exist in L
    SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
>;

// export default function <S extends { [key: string]: CType[keyof CType] }>(
//   schema: S
// ): <C extends { [key: string]: any }, D extends { [key: string]: any }>(
//   config: C,
//   defaults?: D
// ) => Pick<Spread<C, D>, keyof S>;

//type ReturnType<S> =

type Filter<Base, Condition> = {
  [Key in keyof Base]: Base[Key] extends Condition ? Key : never;
}[keyof Base];

type FilterReturnType<S> = {
  [Key in keyof S]: S[Key] extends OConfigType | RConfigType ? Key : never;
}[keyof S];

type GetReturnType<S> = {
  [Key in keyof S]: S[Key] extends OConfigType<infer T> | RConfigType<infer T>
    ? T
    : never;
};

type ReturnType<S> = GetReturnType<Pick<S, FilterReturnType<S>>>;

type ReturnSchema<S> = ReturnType<Pick<S, Filter<S, RConfigType<any>>>> &
  ReturnType<Partial<Pick<S, Filter<S, OConfigType<any>>>>>;

type ReturnConfig<S, C> = {
  [K in keyof S]: C[K];
};

interface ReturnFunction<S> {
  <C extends S, D extends S>(config: C, defaults?: D = {}): ReturnConfig<
    S,
    Spread<C, D>
  >;
}

export default function <
  S extends {
    [key: string | number]: OConfigType | RConfigType;
  }
>(schema: S): ReturnFunction<ReturnSchema<S>>;

除了读取configdefaults的值以更新最终对象之外,这完成了我试图实现的大部分功能。但至少现在函数的输出是基于您输入的blueprint对象的。

现在,正确定义了configdefaults对象的预期输入:

并且正确定义了输出对象。

 类似资料:
  • TypeScript中有些独特的概念可以在类型层面上描述JavaScript对象的模型。 这其中尤其独特的一个例子是“声明合并”的概念。 理解了这个概念,将有助于操作现有的JavaScript代码。 同时,也会有助于理解更多高级抽象的概念。 对本文件来讲,“声明合并”是指编译器将针对同一个名字的两个独立声明合并为单一声明。 合并后的声明同时拥有原先两个声明的特性。 任何数量的声明都可被合并;不局限

  • TS 玩的顺溜不顺溜,就看你的 d.ts 文件写的溜不溜。 在学如何书写声明文件之前,我们先来看看声明相关的一些东西。 接口合并 当我们多次使用 interface 定义的时候,会合并接口 这里报错的原因是,我们并没有完全的实现 A 接口。 错误提示告诉我们,还有一个 age 属性没有。 假如你使用的 2.1 版本的 ts,那么你可以用 keyof 关键字拿到 A 的所有属性值类型。 命名空间的合

  • TypeScript有一些独特的概念,有的是因为我们需要描述JavaScript顶级对象的类型发生了哪些变化。 这其中之一叫做声明合并。 理解了这个概念,对于你使用TypeScript去操作现有的JavaScript来说是大有帮助的。 同时,也会有助于理解更多高级抽象的概念。 首先,在了解如何进行声明合并之前,让我们先看一下什么叫做声明合并。 在这个手册里,声明合并是指编译器会把两个相同名字的声明

  • 问题内容: 有人可以解释一下这是什么意思吗? 这似乎是一个循环定义,至少可以说让我感到困惑。 问题答案: Java Generics FAQ中 有很好的解释。 从末尾开始: 概括起来,声明可以解密为:是仅可为其子类型实例化的泛型类型,并且这些子类型将继承一些有用的方法,其中一些方法具有子类型特定的参数(否则取决于子类型)。 (尽管我确实很同情-递归的泛型声明很痛苦。但是,我的协议缓冲端口到C#的情

  • 问题内容: 注意:纯粹出于好奇而不是用于任何实际用例。 我想知道是否有一种方法可以 使用有效的类型参数声明对象: 如果和是可以互换的,为什么和不呢? 编辑: 问题可以推广到嵌套原始类型参数的问题。例如: 编辑2: 我应该稍微改一下这个问题:我知道 是有效的,但我想知道为什么与 问题答案: 泛型有一些相当严重的局限性。在这种情况下,您不能将类型分配给内部类型,因为您实际上是在引用原始类型,而不是原始

  • 本文向大家介绍如何使用JSP声明来声明类的对象?,包括了如何使用JSP声明来声明类的对象?的使用技巧和注意事项,需要的朋友参考一下 声明声明了一个或多个变量或方法,您可以稍后在JSP文件中的Java代码中使用它们。在JSP文件中使用变量或方法之前,必须先声明该变量或方法。 以下是JSP声明的语法- 您可以编写与上述语法等效的XML,如下所示- 以下是JSP声明中对象声明的示例-