TypeScript 运行时类型检查指南

LeanCloud 2020/7/31 5:03:53

mistermicheels 原作,授权 New Frontend 翻译。为什么需要额外的类型检查?TypeScript 只在编译期执行静态类型检查!实际运行的是从 TypeScript 编译的 JavaScript,这些生成的 JavaScript 对类型一无所知。编译期静态类型检查在代码库内部能发挥很大作用,但对不合规范…

mistermicheels 原作,授权 New Frontend 翻译。

为什么需要额外的类型检查?

TypeScript 只在编译期执行静态类型检查!实际运行的是从 TypeScript 编译的 JavaScript,这些生成的 JavaScript 对类型一无所知。编译期静态类型检查在代码库内部能发挥很大作用,但对不合规范的输入(比如,从 API 处接收的输入)无能为力。

运行时检查的严格性

  • 至少需要和编译期检查一样严格,否则就失去了编译期检查提供的保证。
  • 如有必要,可以比编译期检查更严格,例如,年龄需要大于等于 0。

运行时类型检查策略

定制代码手动检查

  • 灵活
  • 可能比较枯燥,容易出错
  • 容易和实际代码脱节

使用校验库手动检查

比如使用 joi:

import Joi from "@hapi/joi"const schema = Joi.object({    firstName: Joi.string().required(),    lastName: Joi.string().required(),    age: Joi.number().integer().min(0).required()});
  • 灵活
  • 容易编写
  • 容易和实际代码脱节

手动创建 JSON Schema

例如:

{  "$schema": "http://json-schema.org/draft-07/schema#",  "required": [    "firstName",    "lastName",    "age"  ],  "properties": {    "firstName": {      "type": "string"    },    "lastName": {      "type": "string"    },    "age": {      "type": "integer",      "minimum": 0    }  }}
  • 使用标准格式,有大量库可以校验。
  • JSON 很容易存储和复用。
  • 可能会很冗长,手写 JSON Schema 可能会很枯燥。
  • 需要确保 Schema 和代码同步更新。

自动创建 JSON Schema

  • 基于 TypeScript 代码生成 JSON Schema
    -- 比如 typescript-json-schema 这个工具就可以做到这一点(同时支持作为命令行工具使用和通过代码调用)。
    -- 需要确保 Schema 和代码同步更新。
  • 基于 JSON 输入示例生成
    -- 没有使用已经在 TypeScript 代码中定义的类型信息。
    -- 如果提供的 JSON 输入示例和实际输入不一致,可能导致错误。
    -- 仍然需要确保 Schema 和代码同步更新。

转译

例如使用 ts-runtime。

这种方式会将代码转译成功能上等价但内置运行时类型检查的代码。

比如,下面的代码:

interface Person {    firstName: string;    lastName: string;    age: number;}const test: Person = {    firstName: "Foo",    lastName: "Bar",    age: 55}

会被转译为:

import t from "ts-runtime/lib";const Person = t.type(    "Person",    t.object(        t.property("firstName", t.string()),        t.property("lastName", t.string()),        t.property("age", t.number())    ));const test = t.ref(Person).assert({    firstName: "Foo",    lastName: "Bar",    age: 55});

这一方式的缺陷是无法控制在何处进行运行时检查(我们只需在输入输出的边界处进行运行时类型检查)。

顺便提一下,这是一个实验性的库,不建议在生产环境使用。

运行时类型派生静态类型

比如使用 io-ts 这个库。

这一方式下,我们定义运行时类型,TypeScript 会根据我们定义的运行时类型推断出静态类型。

运行时类型示例:

import t from "io-ts";const PersonType = t.type({  firstName: t.string,  lastName: t.string,  age: t.refinement(t.number, n => n >= 0, 'Positive')})

从中提取相应的静态类型:

interface Person extends t.TypeOf<typeof PersonType> {}

以上类型等价于:

interface Person {    firstName: string;    lastName: string;    age: number;}
  • 类型总是同步的。
  • io-ts 很强大,比如支持递归类型。
  • 需要将类型定义为 io-ts 运行时类型,这在定义类时不适用:
    -- 有一种变通的办法是使用 io-ts 定义一个接口,然后让类实现这个接口。然而,这意味着每次给类增加属性的时候都要更新 io-ts 类型。
  • 不容易复用接口(比如前后端之间使用同一接口),因为这些接口是 io-ts 类型而不是普通的 TypeScript 类型。

基于装饰器的类校验

比如使用 class-validator 这个库。

  • 基于类属性的装饰器。
  • 和 Java 的 JSR-380 Bean Validation 2.0 (比如 Hibernate Validator 就实现了这一标准)很像。
    -- 此类 Java EE 风格的库还有 typeorm (ORM 库,类似 Java 的 JPA)和 routing-controllers (用于定义 API,类似 Java 的 JAX-RS)。

代码示例:

import { plainToClass } from "class-transformer";import {     validate, IsString, IsInt, Min } from "class-validator";class Person {    @IsString()    firstName: string;    @IsString()    lastName: string;    @IsInt()    @Min(0)    age: number;}const input: any = {    firstName: "Foo",    age: -1};const inputAsClassInstance = plainToClass(    Person, input as Person);validate(inputAsClassInstance).then(errors => {    // 错误处理代码});
  • 类型总是同步的。
  • 需要对类进行检查时很有用。
  • 可以用来检查接口(定义一个实现接口的类)。

注意:class-validator 用于具体的类实例。在上面的代码中,我们使用它的姊妹库 class-transformer 将普通输入转换为 Person 实例。转换过程本身不进行任何类型检查。

Photo by John Schnobrich on Unsplash

随时随地学软件编程-关注百度小程序和微信小程序
关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。
本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。
[TypeScript 运行时类型检查指南]http://www.zyiz.net/tech/detail-145441.html

上一篇:从零搭建 Node.js 企业级 Web 服务器(九):配置项

下一篇:VUE-多文件断点续传、秒传、分片上传

赞(0)

共有 条评论 网友评论

验证码: 看不清楚?
    关注微信小程序
    程序员编程王-随时随地学编程

    扫描二维码或查找【程序员编程王】

    可以随时随地学编程啦!

    技术文章导航 更多>
    扫一扫关注最新编程教程