有一种情况是,在将参数传递给函数时,我已经知道返回类型,但我很难向TypeScrip解释这一点.

export enum DatePreset {
  Today = "Today",
  ThisMonth = "This month",
  ThisYear = "This year",
  Custom = "Custom"
}

export interface DateRange {
  from: Date;
  to?: Date;
}

export interface DateRangeWithPreset extends DateRange {
  preset: DatePreset;
}

export function applyPreset<T extends DatePreset = DatePreset>(
  preset: T
): T extends DatePreset.Custom ? undefined : Required<DateRangeWithPreset> {
  const presets: Record<
    Exclude<DatePreset, DatePreset.Custom>,
    Required<DateRange>
  > = {
    [DatePreset.Today]: { from: new Date(), to: new Date() },
    [DatePreset.ThisMonth]: { from: new Date(), to: new Date() },
    [DatePreset.ThisYear]: { from: new Date(), to: new Date() }
  };

  // Delete these comments and try to help me solve this problems with types.

  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  // readyPreset: any
  const readyPreset = presets[preset];

  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  return readyPreset && { preset, ...readyPreset };
}

// Right behaviors:

// const validUndefinedRange: undefined
const validUndefinedRange = applyPreset(DatePreset.Custom);

// const thisYearRange: Required<DateRangeWithPreset>
const thisYearRange = applyPreset(DatePreset.ThisYear);

console.log("validUndefinedRange", validUndefinedRange);
console.log("thisYearRange", thisYearRange);

有一个函数可以返回带有From、To和Preset值的对象.返回的范围取决于参数. 在我的例子中,DatePreset.Custom参数将始终返回UNDefined,其余的将返回Object. 我找到了一个有效的解决方案,但埃斯林特和TS不喜欢.请帮我正确输入函数.

Here CodeSandbox.

推荐答案

如果您想保持函数的原样,返回依赖于generic类型参数的conditional type,那么让编译器验证类型安全确实是不可能的.有关允许编译器以某种方式验证这些函数的安全性的特征请求,请参见microsoft/TypeScript#33912.目前还没有人支持这一点.因此,您必须自己承担类型安全验证的负担,并告诉编译器不要担心.

除非万不得已,否则我永远不会建议您使用the // @ts-ignore directive.这基本上只是 suppress 而不是修复错误.相反,您可以像这样使用type assertions:

export function applyPresetAssert<T extends DatePreset = DatePreset>(
  preset: T
) {
  const presets: Record<
    Exclude<DatePreset, DatePreset.Custom>,
    Required<DateRange>
  > & { [DatePreset.Custom]?: undefined } = { 
  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // tell the compiler about this property being undefined
    [DatePreset.Today]: { from: new Date(), to: new Date() },
    [DatePreset.ThisMonth]: { from: new Date(), to: new Date() },
    [DatePreset.ThisYear]: { from: new Date(), to: new Date() }
  };

  const readyPreset = presets[preset]; // okay

  return (readyPreset && { preset, ...readyPreset }) as 
  (undefined | Required<DateRangeWithPreset>) as 
  (T extends DatePreset.Custom ? undefined : Required<DateRangeWithPreset>);
}

最后的as个表达式序列是您需要执行的断言,以便以编译器友好的方式 suppress 错误.您告诉编译器,可以将结果值视为该联合类型,然后将其视为条件类型.

如果您在实现中犯了错误,编译器可能不会捕获它,因此您必须小心.


但您说您更关心编译器验证的类型安全,而不是保持函数的原样.因此,您可以重构以获得编译器能够理解的行为.必须放弃泛型条件类型.相反,您可以通过使用泛型键索引到对象来获得类似的行为;如果您有一个类型为Obj的值obj和一个泛型类型为K的键k,那么编译器将理解obj[k]the indexed access type Obj[K].

因此,让我们重构,这样您就可以执行单个对象查找:

export function applyPreset<T extends DatePreset = DatePreset>(
  preset: T
) {
  const wrap = <T extends object>(x: T) => ({ preset, ...x });
  const presets:
    { [K in DatePreset]:
      K extends DatePreset.Custom ? undefined : Required<DateRangeWithPreset>
    } = {
    get [DatePreset.Today]() { return wrap({ from: new Date(), to: new Date() }) },
    get [DatePreset.ThisMonth]() { return wrap({ from: new Date(), to: new Date() }) },
    get [DatePreset.ThisYear]() { return wrap({ from: new Date(), to: new Date() }) },
    [DatePreset.Custom]: undefined
  };
  return presets[preset];
}

因此,presets对象不再是一个静态对象,而是作为preset参数的函数动态创建的.我使用了getters,因此只有实际访问的属性才会导致代码运行,但您不必这样做.编译器可以验证presets的类型是否与其annotated type匹配:对于除Custom之外的所有DatePreset个键,属性值的类型为Required<DateRangeWithPreset>,否则为undefined.

然后我们只返回presets[preset],编译器认为它是

{
    Today: Required<DateRangeWithPreset>;
    "This month": Required<DateRangeWithPreset>;
    "This year": Required<DateRangeWithPreset>;
    Custom: undefined;
}[T]

让我们来测试一下:

const validUndefinedRange = applyPreset(DatePreset.Custom);
// const validUndefinedRange: undefined
console.log("validUndefinedRange", validUndefinedRange);
// "validUndefinedRange",  undefined 

const thisYearRange = applyPreset(DatePreset.ThisYear);
// const thisYearRange: Required<DateRangeWithPreset>
console.log("thisYearRange", thisYearRange);
/* "thisYearRange",  {
  "preset": "This year",
  "from": "2022-10-01T16:25:21.296Z",
  "to": "2022-10-01T16:25:21.296Z"
*/

看上go 不错.当您调用函数时,编译器仍然会给出正确的类型,而实现仍然会给出正确的值.而且您不必 suppress 任何编译器错误.

Playground link to code

Typescript相关问答推荐

Angular 过滤器形式基于某些条件的数组控制

TypeScript泛型参数抛出错误—?&#"' 39;预期"

如何用不同的键值初始化角react 形式

泛型函数即使没有提供类型

如何键入函数以只接受映射到其他一些特定类型的参数类型?

类型脚本`Return;`vs`Return unfined;`

如何根据特定操作隐藏按钮

TypeScrip-将<;A、B、C和>之一转换为联合A|B|C

在函数中打字推断记录的关键字

如何将v-for中的迭代器编写为v-if中的字符串?

不带其他属性的精确函数返回类型

为什么发送空字段?

如何使用TypeScrip在EXPO路由链路组件中使用动态路由?

Cypress-验证别名的存在或将别名中的文本与';if';语句中的字符串进行比较

T的typeof键的Typescript定义

Typescript:是否将联合类型传递给重载函数?

如何为字符串模板文字创建类型保护

是否可以使用元组中对象的键来映射类型

在Typescript 中,是否有一种方法来定义一个类型,其中该类型是字符串子集的所有可能组合?

如何创建对象数组中特定键中使用的所有值的类型?