打字稿 力量永不

当我第一次看到“永不”​​这个词时,我以为TypeType在TypeScript中显得毫无用处。 随着时间的流逝,我深入了解ts,开始理解这个词的作用力。 而这种能力源于我打算与读者分享的实际用例。 谁在乎,欢迎来猫。

从来没有


如果深入研究历史,我们将看到TypeScript在2.0版TypeScript诞生之初就从未出现过,只是对其目的进行了较为适度的描述 。 如果您简短自由地讲述ts开发人员的版本,则该类型从不为原始类型,该原始类型表示永远不会发生的值的符号。 或者,一个因其循环(例如,无限循环)或由于其中断而永不返回值的函数的符号。 为了清楚地说明所讲内容的实质,我建议看以下示例:

/**    */ function error(message: string): never { throw new Error(message); } /**   */ function infiniteLoop(): never { while (true) { } } /**   */ function infiniteRec(): never { return infiniteRec(); } 

也许由于这样的例子,我给人的第一印象是类型需要清晰。

类型系统


现在我可以说,TypeScript中的丰富类型系统动物群也永远不会到期。 为了支持我的话,我将提供lib.es5.d.ts中的几种库类型

 /** Exclude from T those types that are assignable to U */ type Exclude<T, U> = T extends U ? never : T; /** Extract from T those types that are assignable to U */ type Extract<T, U> = T extends U ? T : never; /** Construct a type with the properties of T except for those in type K. */ type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; /** Exclude null and undefined from T */ type NonNullable<T> = T extends null | undefined ? never : T; /** Obtain the parameters of a function type in a tuple */ type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never; 

在我从未使用过的类型中,我将给出我最喜欢的-GetNames,它是keyof的改进版:

 /** * GetNames      * @template FromType  -   * @template KeepType   * @template Include       .   false -    KeepType */ type GetNames<FromType, KeepType = any, Include = true> = { [K in keyof FromType]: FromType[K] extends KeepType ? Include extends true ? K : never : Include extends true ? never : K }[keyof FromType]; //   class SomeClass { firstName: string; lastName: string; age: number; count: number; getData(): string { return "dummy"; } } // be: "firstName" | "lastName" type StringKeys = GetNames<SomeClass, string>; // be: "age" | "count" type NumberKeys = GetNames<SomeClass, number>; // be: "getData" type FunctionKeys = GetNames<SomeClass, Function>; // be: "firstName" | "lastName" | "age" | "count" type NonFunctionKeys = GetNames<SomeClass, Function, false>; 

监控未来的变化


当项目的寿命不短暂时,开发就具有特殊性。 我想控制代码中的关键点,以确保您或您的团队成员在花费时间时不会忘记修改重要的代码。 当存在某种状态或枚举时,此类代码部分尤其明显。 例如,列出实体上的一组动作。 我建议看一个例子以了解正在发生的情况。

 //    -   ,     ,   ActionEngine type AdminAction = "CREATE" | "ACTIVATE"; // ,     ,   AdminAction   . class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE": //   return "CREATED"; case "ACTIVATE": //   return "ACTIVATED"; default: throw new Error("   "); } } } 

上面的代码只是为了着重于重点而尽可能地简化了-AdminAction类型是在另一个项目中定义的,甚至可能没有团队陪伴。 由于该项目将持续很长时间,因此有必要在不知情的情况下保护ActionEngine免受AdminAction类型的更改。 TypeScript提供了几种解决此问题的方法,其中之一是使用Never类型。 为此,我们需要定义NeverError并在doAction方法中使用它。

 class NeverError extends Error { //         - ts   constructor(value: never) { super(`Unreachable statement: ${value}`); } } class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE": //   return "CREATED"; case "ACTIVATE": //   return "ACTIVATED"; default: throw new NeverError(action); // ^       switch  . } } } 

现在将新的“ BLOCK”值添加到AdminAction并在编译时出现错误:类型“ BLOCK”的参数不能分配给类型“ never” .ts的参数(2345)。

原则上,我们实现了这一目标。 值得一提的是有趣的一点是,开关构造可保护我们避免更改AdminAction元素或将其从集合中删除。 从实践中,我可以说它确实按预期工作。

如果您不想引入NeverError类,则可以通过声明一个Never类型的变量来控制代码。 像这样:

 type AdminAction = "CREATE" | "ACTIVATE" | "BLOCK"; class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE": //   return "CREATED"; case "ACTIVATE": //   return "ACTIVATED"; default: const unknownAction: never = action; // Type '"BLOCK"' is not assignable to type 'never'.ts(2322) throw new Error(`   ${unknownAction}`); } } } 

上下文限制:此+永不


以下技巧常常使我免于因疲劳或粗心而造成的可笑错误。 在下面的示例中,我不会评估所选方法的质量。 事实上,与我们一起发生这种情况。 假设您在类中使用了无法访问该类字段的方法。 是的,这听起来很可怕-所有这些都是gov ...代码。

 @SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook() { //    ,    ,     SomeUiPanel this.someService.doInit("Bla bla"); // ^       :  beforeAccessHook   ,      } } 

在更广泛的情况下,它可以是回调或箭头函数,它们具有自己的执行上下文。 任务是:如何保护自己免受运行时错误的影响? 为此,TypeScript可以指定上下文。

 @SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook(this: never) { //    ,    ,     SomeUiPanel this.someService.doInit("Bla bla"); // ^ Property 'someService' does not exist on type 'never' } } 

公平地说,我不会说这是不值得的。 相反,您可以使用void和{}。 但是,这种类型永远不会在您阅读代码时引起注意。

期望值


不变量


有一个绝对的想法,我认为以下代码应该可以工作:

 type Maybe<T> = T | void; function invariant<Cond extends boolean>(condition: Cond, message: string): Cond extends true ? void : never { if (condition) { return; } throw new Error(message); } function f(x: Maybe<number>, c: number) { if (c > 0) { invariant(typeof x === "number", "When c is positive, x should be number"); (x + 1); // works because x has been refined to "number" } } 

但是a。 表达式(x + 1)引发错误:运算符'+'不能应用于类型'Maybe'和'1'。 在文章30,000行代码从Flow到TypeScript的传输中,我监视了示例本身

灵活的绑定


我认为,在永不实现的帮助下,我可以控制必需的函数参数,并在某些情况下禁用不必要的参数。 但是不,那是行不通的:

 function variants<Type extends number | string>(x: Type, c: Type extends number ? number : never): number { if (typeof x === "number") { return x + c; } return +x; } const three = variants(1, 2); // ok // 2  - never,     string. ,   const one = variants("1"); // expected 2 arguments, but got 1.ts(2554) 

上述问题以不同的方式解决。

更严格的验证


我希望ts编译器不要错过与常识相反的东西。

 variants(<never> {}, <never> {}); 

结论


最后,我想提供一系列小怪异的小任务。 错误是哪一行?

 class never<never> { never: never; } const whats = new never<string>(); whats.never = ""; 

选件
在后者中:类型'“”'不能分配给类型'never'.ts(2322)

这就是我永远都不想说的。 谢谢大家的关注,我们很快再见。

Source: https://habr.com/ru/post/zh-CN471026/


All Articles