TypeScript 通用运行时类型检查

TypeScript generic runtime type check

运行时数据是否可以指定类型进行运行时类型检查?希望使用 io-ts?

一条switch语句创建了多个位置来添加新类型。查找像 types[runtime.type] 这样的对象属性会产生编译时类型检查错误。值可能未定义。

运行时数据:

[{label:"user", userid:1}, {label:"post", body:"lipsum"}]

类型:

type User = {
  label: 'user'
  userid: number
}

type Delete = {
  label: 'post'
  body: string
}

检查类型后,我还想在通用实现中使用数据:

function save<A>(data:A) {
  mutate<A>(data)
  validate<A>(data)
  send<A>(data)
}

这叫做相关记录类型,在 TypeScript 社区有很多讨论。我的做法是将相关记录检查移动到运行time:

https://github.com/microsoft/TypeScript/issues/30581#issuecomment-686621101

https://repl.it/@urgent/runtime-fp-ot

第 1 步

创建一个包含所有允许属性的通用 Prop 界面。

interface Prop {
  label: "user" | "post",
  userid: string,
  body: string
}

这确实会阻止类型特定的属性,并且您可能会发生冲突。 user.id 想成为 number,但 post.id 想成为 string。对我来说没什么大不了的。考虑一个特定于您的类型的不同 属性 名称,接受那里的名称,或者如果您有冒险精神,请尝试向 Props 添加维度并按类型建立索引。

第 2 步

创建标签到 运行 时间解码器的映射。在打字稿中我使用 class 所以我可以通过不同的文件扩展它:

class Decoder {
  user: userDecode,
  post: postDecode
}

步骤 3

创建一个接受 props 的函数,从 class 原型中查找解码器,并执行 运行time decode

(props:Props) => Decoder.prototype[props.label].decode(props as any)
在严格模式下

io-ts 需要在此处加宽 any。您还需要检查 props.label 是否存在于 Decoder

第 4 步

创建与 运行 类似的函数映射。如果您在 运行time 解码之后调用它们,您就会知道 运行time 正在传递有效值。

缺点

  1. 比编写和管理 switch 语句要复杂得多。 TypeScript 类型自动缩小 switch
  2. 属性 碰撞。没有添加工作就没有该类型的特定属性。
  3. 需要手动检查类型是否存在。 switch 将忽略,并使用默认大小写

优点

  1. 您可以永久关闭运行时间处理。隔离,如果您需要添加对不同 运行 时间类型的支持,请不要打开该代码。
  2. 当创建对不同 运行 时间类型的支持时,例如 pagefile,像 babel 文件这样的东西可以自动导入新文件。
  3. 适用于版本控制和开发人员访问权限。可以打开新类型以供 public 提交。核心处理可以保持关闭。这样你就有了 DSL 的开始。

如果您的标签在所有类型中都是通用的,如果您对要处理的每种情况进行测试,则可以使用类型保护轻松处理。

对于标记为返回 x is y 的函数,如果它 returns true,那么 编译器 知道在 true 部分 if,变量是该类型,在 else 中,不是该类型。

const data = [{label:"user", userid:1}, {label:"post", body:"lipsum"}]

type User = {
  label: 'user'
  userid: number
}

function isUser(u: { label: string }): u is User {
  return u.label === 'user';
}

type Delete = {
  label: 'post'
  body: string
}

function isDelete(d: { label: string }): d is Delete {
  return d.label === 'post';
}

for (const datum of data) {
  if (isUser(datum)) {
    console.log(`User with userid of ${datum.userid}`);
  } else if (isDelete(datum)) {
    console.log(`Delete with body of ${datum.body}`);
  }
}

TypeScript Playground
User Defined Type Guards