放置TypeScript枚举和布尔值

2020年12月30日11:19:22 发表评论 30 次浏览

本文概述

我喜欢TypeScript的崭新世界的第一件事就是TypeScript枚举。我以前在C#中使用过它们, 因此感到放心。

枚举是一组命名常量, 可以采用数字或字符串形式。我一直使用基于字符串的枚举, 在本文中的所有示例中都将使用该枚举:

enum State {
  on = 'ON', off = 'OFF, };

使用不当

我在各种不适当的地方使用了枚举, 例如String在之前输入Redux操作@ reduxjs /工具包帮助减轻了臭名昭著的Redux样板:

enum AuthActionTypes {
  SetForcePasswordChange = "SET_PASSWORD_CHANGE"
}

interface ForcePasswordChange {
type: AuthActionTypes.SetForcePasswordChange;
}

export const forcePasswordChange = (): ForcePasswordChange => ({
  type: AuthActionTypes.SetForcePasswordChange
});

我的动机是避免烦人的字符串错字错误, 对于此要求, 它可以奏效。

手持新锤, 一切看起来都像钉子

当我在构建时和运行时同时存在的这种新的诱人的科学怪人构造增加了信心时, 我变得更加冒险。

枚举似乎是在有限状态机中建模状态的绝佳选择。

当时我不知道的是, 我缺少TypeScript最杰出的功能之一, 而不仅仅是确保我拥有一组互斥的常量, 这远远超过了:

enum AuthenticationStates {
  unauthorised = "UNAUTHORISED", authenticating = "AUTHENTICATING", authenticated = "AUTHENTICATED", errored = "ERRORED", forcePasswordChange = "FORCE_PASSWORD_CHANGE"
}

在上面的示例中, 我有一个认证状态为身份验证工作流程建模的枚举。

用户开始于未经授权过渡到之前的状态认证等等。我精心设计的枚举可确保用户在任何时候都不会处于一种以上矛盾的状态。例如, 它们不能是认证的和认证.

我需要更多果汁

然后我意识到我将需要其他数据, 例如, 如果发生错误, 那么我将需要知道实际的数据是什么。错误对象是。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

放置TypeScript枚举和布尔值1

最初, 我对新需求进行了如下建模:

enum AuthenticationStates {
  unauthorised = "UNAUTHORISED", authenticating = "AUTHENTICATING", authenticated = "AUTHENTICATED", errored = "ERRORED", }

type State = {
  current: AuthenticationStates;
  isLoading: boolean;
  authToken?: string;
  error?: Error;
};

const current: State = {
  kind: AuthenticationStates.authenticated, isLoading: false, authToken: 'token', error: undefined
};

我努力确保每个身份验证状态都可以具有相同类型的字段。

这种方法的问题在于, 每个状态都具有相同的字段, 并且可能成为错误的重要来源, 因为我可能会变得懒惰并开始复制和粘贴。

然后, 我发现了有区别的并集, 也称为代数数据类型。

区分并集又称代数数据类型

如果你想在聚会上给人留下深刻印象, 那么告诉他们每天使用代数数据类型是可以保证的本垒打!

在TypeScript中, 我们可以创建一个类似于我们的字符串联合认证方式枚举:

type AuthenticationStates =
  | "UNAUTHORISED"
  | "AUTHENTICATING"
  | "AUTHENTICATED"
  | "ERRORED";

我可以将其用作更强大的字符串参数类型, 从而更好地保证允许使用哪些值。

TypeScript中的并集可以是许多事物的并集, 而不仅仅是原始类型。我们可以通过创建一个只需要拥有相同条件的工会来使世界变得更美好类字段作为联合中的每个元素。的类字段将充当区分符:

export type AuthenticationStates =
  | {
      kind: "UNAUTHORISED";
      context: {
        isLoading: false
      };
    }
  | {
      kind: "AUTHENTICATING";
      context: {
        isLoading: true;
      };
    }
  | {
      kind: "AUTHENTICATED";
      context: {
        isLoading: false;
        authToken: string;
      };
    }
  | {
      kind: "ERRORED";
      context: { isLoading: false; error: Error };
    };

上面的类型都是漂亮的可执行文档, 我们一眼就能看到工作流中所有可用的状态。

上例中的鉴别符是类编译器用于的字段窄型或应用更具体的规则, 因为它确定变量可能是联合的确切元素。

这里的关键要点是, 每种类型只有适当的数据可用。

我们没有业务试图访问authToken如果我们目前不在认证的州。

更令人兴奋的是, 与在代码检查期间在JVM上花费过多时间的程序员相比, 编译器可以更好地执行此正确性顺序。

歧视工会上的类型变窄

下图说明了TypeScript如何在联合的鉴别符上键入狭窄的字符:

const transition = (state: AuthenticationStates) => {
  switch (state.kind) {
    case "UNAUTHORISED": {
      console.log(state.context.userName); // only available in UNAUTHORISED

      // this is hot!! the compiler will not allow us to access the authToken in this state
      console.log(state.context.authToken); // Property 'authToken' does not exist on type '{ isLoading: false; userName: string; password: string; }'
      break;
    }
    case "AUTHENTICATING":
      console.log(state.context.userName); // Property 'userName' does not exist on type '{ isLoading: true; }'.
      // Type 'false' is not assignable to type 'true'
      state.context.isLoading = false;
      // The only assignable value is true in this state
      state.context.isLoading = true;
      break;
    case "AUTHENTICATED":
      // here and only here do we have an authToken
      console.log(state.context.authToken);
      break;
    case "ERRORED":
      console.log(state.context.error);
      break;
  }
};

你也可以签出CodeSandbox这里.

仅当编译器使用以下命令缩小类型时, 我们才能访问特定数据类鉴别字段。

最好的例子是这样的:

未经授权的讯息

如果我们尝试使用authToken处于错误状态。

布尔不对状态建模

用布尔值对状态建模的任何尝试都将失败, 因为矛盾的变量和开发人员压力很大。

例如, 如果我们尝试这种方法:

const isAuthenticated: boolean = false;
const isErrored: boolean = false;
const isLoading: boolean = false;

不久之后, 我们就开始将这些吸盘组合成一团混乱的逻辑, 并且不断旋转。

if (isErrored &&. isAuthenticated === false) {
  // do this
} else if (isLoading && is isErrored) {
  // do something else

代数在哪里?

这些听起来花哨的代数数据类型无非是说一种类型由其他类型组成的方式。这就对了。根本没有那么花哨。

结语

联合会充当美观的可执行文档, 并且使错误的程序员保持直截了当, 这在以前的JavaScript编程的Wild West领域尤为普遍。

代数数据类型已经存在于诸如Haskell之类的功能语言中已有一段时间了, 令人振奋的是TypeScript将它们带给了前端开发人员大手笔。

日志火箭:全面了解你的网络应用

LogRocket仪表板免费试用横幅

日志火箭是一个前端应用程序监视解决方案, 可让你重播问题, 就好像问题发生在你自己的浏览器中一样。 notlogy无需猜测错误发生的原因, 也不要求用户提供屏幕截图和日志转储, 而是让你重播会话以快速了解出了什么问题。无论框架如何, 它都能与任何应用完美配合, 并具有用于记录来自Redux, Vuex和@ ngrx / store的其他上下文的插件。

除了记录Redux动作和状态外, notlogy还会记录控制台日志, JavaScript错误, 堆栈跟踪, 带有标题+正文, 浏览器元数据和自定义日志的网络请求/响应。它还使用DOM来记录页面上的HTML和CSS, 甚至可以为最复杂的单页面应用程序重新创建像素完美的视频。

免费试用

.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: