使用never类型使开关语句超级安全的指南

65 阅读2分钟

Photo by Wendelin Jacober https://www.pexels.com/@wendelinjacober?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels from Pexels

问题所在

下面的代码是一个计数器组件的简单还原器。它包含一个在联合类型上的switch 语句,这是相当安全的类型:

type Increment = {
  type: "increment";
  incrementStep: number;
};
type Decrement = {
  type: "decrement";
  decrementStep: number;
};
type Actions = Increment | Decrement;

const reducer = (state: State, action: Actions): State => {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.incrementStep };
    case "decrement":
      return { count: state.count - action.decrementStep };
  }
};

我们怎样才能使这个类型更加安全呢?如果我们有一个新的要求,要重置计数器,怎么办?这将意味着一个新的动作被添加到Actions 类型中。当我们添加新的动作时,如何让TypeScript编译器提醒我们需要实现switch 语句中的一个新分支?

解决方案

如果我们可以告诉TypeScript编译器,switch 语句中的default 分支不应该被达到呢?好吧,我们可以用never 类型来做到这一点!

让我们研究一下actiondefault 分支中的类型是什么:

const reducer = (state: State, action: Actions): State => {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.incrementStep };
    case "decrement":
      return { count: state.count - action.decrementStep };
    default:
      action;
  }
};

default never 1

它的类型是never!

因此,如果我们创建一个假函数,接收一个类型为never 的参数,在default switch 分支中调用,也许TypeScript编译器会在到达那里时出错......

const reducer = (state: State, action: Actions): State => {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.incrementStep };
    case "decrement":
      return { count: state.count - action.decrementStep };
    default:
      neverReached(action);
  }
};

const neverReached = (never: never) => {};

让我们添加一个Reset 动作并试一试:

type Reset = {
  type: "reset";
  to: number;
};
type Actions = Increment | Decrement | Reset;

default never2

TypeScript编译器如我们所愿出错。很好!

结束语

调用一个假函数,它的参数类型是never ,这是一个很好的方法,可以让TypeScript编译器在我们的程序到达一个不应该到达的区域时发出警告。在联盟类型的switch 语句中,这是一个很好的提示,这样我们就会被提醒在联盟类型被改变时实现一个额外的分支。

如果你想了解更多关于never 类型的信息,Marius Schulz 有一篇很棒的文章