运行时类型

在运行时在TypeScript中提供类型信息变化很大。它允许采用新的工作方式,而这在以前只能以迂回的方式或根本不可能实现。声明类型和模式已经成为现代开发过程中的一个重要部分。GraphQL、验证器、ORM、ProtoBuf等编码器以及其他许多东西都依赖于在运行时有模式信息可用,以提供基本功能。这些工具和库有时需要开发人员学习全新的语言,这些语言是专门为使用情况开发的。例如,ProtoBuf和GraphQL有自己的声明语言,验证器通常基于自己的模式API,甚至是JSON模式,这也是一种独立的定义结构的方式。其中一些需要在每次更改时运行代码生成器,以便将模式信息也提供给运行时。另一个著名的模式是使用实验性的TypeScript装饰器,在运行时向类提供元信息。

但这一切有必要吗?TypeScript提供了一个非常强大的语言来描述甚至非常复杂的结构。事实上,TypeScript现在是游刃有余的,这大致意味着理论上任何种类的程序都可以映射到TypeScript中。当然,这有其实际的局限性,但重要的一点是,TypeScript能够完全取代任何声明格式,如GraphQL、ProtoBuf、JSON Schema和许多其他格式。在运行时与类型系统相结合,有可能涵盖所有描述的工具和它们在TypeScript本身的使用情况,而不需要任何代码生成器。但是,为什么仍然没有确切的解决方案呢?

从历史上看,TypeScript在最近几年经历了巨大的变化。它已经被完全重写了好几次,获得了基本的功能,并经历了一系列的迭代和突破性变化。然而,与此同时,TypeScript已经达到了产品市场的契合度,大大减缓了基本创新和突破性变化发生的速度。TypeScript已经证明了自己,并展示了像JavaScript这样的高度动态语言的高度迷人的类型系统应该是什么样子。市场感激地接受了这一推动,并迎来了用JavaScript开发的新时代。

此时正是在语言本身的基础上建立工具的好时机,使上述情况成为可能。Deepkit希望成为推动力,将几十年来来自Java和PHP等语言企业的成熟设计模式不仅从根本上引入TypeScript,而且以一种新的、更好的方式与JavaScript一起工作,而不是反对它。通过运行时的类型信息,这些现在第一次不仅在原则上是可能的,而且允许全新的更简单的设计模式,这在Java和PHP等语言中是不可能的。

在运行时读取类型信息是Deepkit建立其基础的能力。Deepkit库的API在很大程度上是为了尽可能多地使用TypeScript的类型信息,以便尽可能地提高效率。运行时的类型系统意味着类型信息在运行时是可读的,动态类型是可计算的。这意味着,例如,类的所有属性和函数的所有参数和返回类型都可以被读出来。

以这个函数为例:

function log(message: string): void {
    console.log(message);
}

在JavaScript本身,有几个信息可以在运行时被读出。例如,函数的名称(如果没有用最小化器修改):

log.name; //‘log’

另一方面,可以读出参数的数量:

log.length; //1

只要多写一点代码,也可以读出参数的名称。然而,如果没有一个简陋的JavaScript解析器或对log.toString()的正则表达法,这是不可能做到的,所以就这样了。由于TypeScript将上述函数翻译成JavaScript,如下:

function log(message) {
    console.log(message);
}

`message`是字符串类型,返回类型是`void`类型的信息不再可用了。

但是,如果在运行时有一个类型系统,这些信息就可以保留下来,这样就可以通过程序读取消息的类型和返回类型。

import { typeOf, ReflectionKind } from '@deepkit/type';

const type = typeOf(log);
type.kind; //ReflectionKind.function
type.parameters[0].name; //'message'
type.parameters[0].type; //{kind: ReflectionKind.string}
type.return; //{kind: ReflectionKind.void}

Deepkit正是让这成为可能。它与TypeScript的编译挂钩,确保所有的类型信息都内置于生成的JavaScript中。像typeOf()这样的函数(不要和运算符typeof混淆,小写的是o)就允许开发者访问它。因此,可以根据这些类型信息来开发库,使开发者可以将已经写好的TypeScript类型用于一系列的应用。

Installation

安装Deepkit的运行时类型系统需要两个包。@deepkit/type-compiler`中的类型编译器和@deepkit/type`中的必要运行时间。类型编译器可以安装在`package.json`_devDependencies_中,因为它只在构建时需要。

npm install --save @deepkit/type
npm install --save-dev @deepkit/type-compiler

运行时类型信息默认不生成。必须在`tsconfig.json`文件中设置`"反射": true`,以便在这个文件的同一文件夹中的所有文件或所有子文件夹中启用它。如果要使用装饰器,`"experimentalDecorators": true`必须在`tsconfig.json`中启用。

文件:tsconfig.json

{
  "compilerOptions": {
    "module": "CommonJS",
    "target": "es6",
    "moduleResolution": "node",
    "experimentalDecorators": true
  },
  "reflection": true
}

Type Compiler

TypeScript本身不允许你通过`tsconfig.json’来配置类型编译器,这对于使用`@deepkit/type’并不是严格必要的,但对于其他deepkit库和`@deepkit/framework’中的某些功能是必要的。有必要直接使用TypeScript编译器API或像Webpack这样的构建系统与_ts-loader_。为了给Deepkit用户省去这个不方便的路径,Deepkit类型编译器一旦安装了`@deepkit/type-compiler`,就会自动安装在`node_modules/typescript`中(这是通过NPM安装挂钩完成的)。 这使得所有访问本地安装的TypeScript(`node_modules/typescript`中的)的构建工具都能自动启用类型编译器。这使得_tsc_、Angular、webpack、_ts-node_和其他一些工具能够自动与deepkit类型编译器一起工作。

如果类型编译器不能成功地自动安装(例如,因为NPM安装钩子被禁用),这可以通过以下命令手动完成:

node_modules/.bin/deepkit-type-install

注意,如果本地的typecript版本已经更新(例如,如果软件包中的typecript版本。

Webpack

如果要在webpack构建中使用类型编译器,可以使用`ts-loader`包(或任何其他支持transformer注册的类型脚本加载器)来完成。)

文件:webpack.config.js

const typeCompiler = require('@deepkit/type-compiler');

module.exports = {
  entry: './app.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
          use: {
            loader: 'ts-loader',
            options: {
              //this enables @deepkit/type's type compiler
              getCustomTransformers: (program, getProgram) => ({
                before: [typeCompiler.transformer],
                afterDeclarations: [typeCompiler.declarationTransformer],
              }),
            }
          },
          exclude: /node_modules/,
       },
    ],
  },
}

类型装饰器

类型装饰器是正常的TypeScript类型,包含元信息,可以在运行时改变各种函数的行为。Deepkit已经提供了一些类型装饰器,涵盖了一些使用情况。例如,一个类属性可以被标记为主键、引用或索引。数据库库可以在运行时使用这些信息来创建正确的SQL查询,而不需要事先生成代码。 Validator约束,如`MaxLength`、Maximum`或`Positive,也可以添加到任何类型。也可以告诉序列化器如何序列化或反序列化一个特定的值。此外,还可以创建完全自定义的类型装饰器,并在运行时读取这些装饰器,从而可以在运行时非常自定义地使用类型系统。

Deepkit带有一整套类型装饰器,所有这些都可以直接从`@deepkit/type`中使用。它们被设计成不来自多个库,这样就不会把代码直接绑在某个特定的库上,如Deepkit RPC或Deepkit数据库。这使得类型的重用更加容易,即使是在前端,即使使用了数据库类型装饰器。

下面是现有类型装饰器的列表。@deepkit/type`和@deepkit/bson`的验证器和序列化器以及`@deepkit/orm`的Deepkit数据库以不同的方式使用这一信息。请参阅相关章节了解更多。

Integer/Float

Integer和floats被定义为基数`number`,并有几个子变量:

Type Description

integer

An integer of arbitrary size.

int8

An integer between -128 and 127.

uint8

An integer between 0 and 255.

int16

An integer between -32768 and 32767.

uint16

An integer between 0 and 65535.

int32

An integer between -2147483648 and 2147483647.

uint32

An integer between 0 and 4294967295.

float

Same as number, but might have different meaning in database context.

float32

A float between -3.40282347e+38 and 3.40282347e+38. Note that JavaScript is not able to check correctly the range due to precision issues, but the information might be handy for the database or binary serializers.

float64

Same as number, but might have different meaning in database context.

import { integer } from '@deepkit/type';

interface User {
    id: integer;
}

这里,在运行时,用户的`id`是一个数字,但在验证和序列化时被解释为一个整数。 这意味着在这里,例如,浮点数可能不会被用于验证,序列化器会自动将浮点数转换为整数。

import { is, integer } from '@deepkit/type';

is<integer>(12); //true
is<integer>(12.5); //false

子类型可以以同样的方式使用,如果要允许一定范围的数字,子类型是很有用的。

import { is, int8 } from '@deepkit/type';

is<int8>(-5); //true
is<int8>(5); //true
is<int8>(-200); //false
is<int8>(2500); //false

Float

UID

UUID v4通常在数据库中存储为二进制,在JSON中存储为字符串。

import { is, UUID } from '@deepkit/type';

is<UUID>('f897399a-9f23-49ac-827d-c16f8e4810a0'); //true
is<UUID>('asd'); //false

MongoID

将此字段标记为MongoDB的ObjectId。解决为一个字符串。

import { MongoId, serialize, is } from '@deepkit/type';

serialize<MongoId>('507f1f77bcf86cd799439011'); //507f1f77bcf86cd799439011
is<MongoId>('507f1f77bcf86cd799439011'); //true
is<MongoId>('507f1f77bcf86cd799439011'); //false

class User {
    id: MongoId = ''; //will automatically set in Deepkit ORM once user is inserted
}

Bigint

默认情况下,普通的bigint类型在JSON中被序列化为数字(在BSON中为长)。然而,这对可能的保存有限制,因为JavaScript中的bigint有无限的潜在大小,而JavaScript中的数字和BSON中的long是有限的。为了绕过这个限制,可以使用`BinaryBigInt`和`SignedBinaryBigInt`类型。

BinaryBigInt`与bigint相同,但在数据库中序列化为无限大小的无符号二进制(而不是大多数数据库的8字节),在JSON中序列化为字符串。负值将被转换为正值(`abs(x))。

import { BinaryBigInt } from '@deepkit/type';

interface User {
    id: BinaryBigInt;
}

const user: User = {id: 24n};

serialize<User>({id: 24n}); //{id: '24'}

serialize<BinaryBigInt>(24); //'24'
serialize<BinaryBigInt>(-24); //'0'

Deepkit ORM将BinaryBigInt存储为二进制字段。

SignedBinaryBigInt’与’BinaryBigInt’相同,但也能存储负值。Deepkit ORM将`SignedBinaryBigInt`存储为二进制。二进制有一个额外的前导符号字节,表示为uint:255表示负,0表示零,1表示正。

import { SignedBinaryBigInt } from '@deepkit/type';

interface User {
    id: SignedBinaryBigInt;
}

MapName

在序列化中改变一个属性的名称。

import { serialize, deserialize, MapName } from '@deepkit/type';

interface User {
    firstName: string & MapName<'first_name'>;
}

serialize<User>({firstName: 'Peter'}) // {first_name: 'Peter'}
deserialize<User>({first_name: 'Peter'}) // {firstName: 'Peter'}

Group

属性可以被归为一组。对于序列化,你可以将一个组排除在序列化之外。

import { serialize } from '@deepkit/type';

interface Model {
    username: string;
    password: string & Group<'secret'>
}

serialize<Model>(
    { username: 'Peter', password: 'nope' },
    { groupsExclude: ['secret'] }
); //{username: 'Peter'}

Data

每个属性都可以添加额外的元数据,可以通过Reflection API读取。更多信息请参见运行时类型反思

import { ReflectionClass } from '@deepkit/type';

interface Model {
    username: string;
    title: string & Data<'key', 'value'>
}

const reflection = ReflectionClass.from<Model>();
reflection.getProperty('title').getData()['key']; //value;

Excluded

每个属性都可以从特定目标的序列化过程中排除。

import { serialize, deserialize, Excluded } from '@deepkit/type';

interface Auth {
    title: string;
    password: string & Excluded<'json'>
}

const item = deserialize<Auth>({title: 'Peter', password: 'secret'});

item.password; //undefined, since deserialize's default serializer is called `json`

item.password = 'secret';

const json = serialize<Auth>(item);
json.password; //again undefined, since serialize's serializer is called `json`

Embedded

将字段标记为嵌入式类型。

import { PrimaryKey, Embedded, serialize, deserialize } from '@deepkit/type';

interface Address {
    street: string;
    postalCode: string;
    city: string;
    country: string;
}

interface User  {
    id: number & PrimaryKey;
    address: Embedded<Address>;
}

const user: User {
    id: 12,
    address: {
        street: 'abc', postalCode: '1234', city: 'Hamburg', country: 'Germany'
    }
};

serialize<User>(user);
{
    id: 12,
    address_street: 'abc',
    address_postalCode: '1234',
    address_city: 'Hamburg',
    address_country: 'Germany'
}

//for deserialize you have to provide the embedded structure
deserialize<User>({
    id: 12,
    address_street: 'abc',
    //...
});

可以改变前缀(默认是属性名称)。

interface User  {
    id: number & PrimaryKey;
    address: Embedded<Address, {prefix: 'addr_'}>;
}

serialize<User>(user);
{
    id: 12,
    addr_street: 'abc',
    addr_postalCode: '1234',
}

//or remove it entirely
interface User  {
    id: number & PrimaryKey;
    address: Embedded<Address, {prefix: ''}>;
}

serialize<User>(user);
{
    id: 12,
    street: 'abc',
    postalCode: '1234',
}

实体

用实体信息注释接口。

import { Entity, PrimaryKey } from '@deepkit/type';

interface User extends Entity<{name: 'user', collection: 'users'> {
    id: number & PrimaryKey;
    username: string;
}

InlineRuntimeType

TODO

ResetDecorator

TODO

Database

TODO: PrimaryKey, AutoIncrement, Reference, BackReference, Index, Unique, DatabaseField.

验证

TODO

用户定义的类型装饰器

一个类型装饰器可以定义如下:

type MyAnnotation = {__meta?: ['myAnnotation']};

按照惯例,一个类型装饰器被定义为一个对象字面,有一个单一的可选属性`__meta`,它的类型是一个元组。这个元组的第一个条目是它的唯一名称,所有进一步的元组条目是任何选项。这允许一个类型装饰器配备额外的选项。

type AnnotationOption<T extends {title: string}> = {__meta?: ['myAnnotation', T]};

类型装饰器与相交运算符`&`一起使用。

type Username = string & MyAnnotation;
type Title = string & & MyAnnotation & AnnotationOption<{title: 'Hello'}>;

类型装饰器可以通过`typeOf<T>()`和`metaAnnotation`的类型对象读出:

import { typeOf, metaAnnotation } from '@deepkit/type';

const type = typeOf<Username>();
const annotation = metaAnnotation.getForName(type, 'myAnnotation'); //[]

如果使用了类型装饰器`myAnnotation`,annotation`中的结果是一个带有选项的数组,如果没有则是`undefined。如果类型装饰器有额外的选项,如在`AnnotationOption`中看到的那样,传递的值将在数组中找到。 已经提供的类型装饰器如`MapName`、Group、`Data`等都有自己的注释对象:

import { typeOf, Group, groupAnnotation } from '@deepkit/type';
type Username = string & Group<'a'> & Group<'b'>;

const type = typeOf<Username>();
groupAnnotation.getAnnotations(type); //['a', 'b']

参见运行时类型反思以了解更多。

外部类

由于TypeScript默认不包含类型信息,所以从其他包(没有使用@deepkit/type-compiler)导入的类型/类将没有类型信息。

要为一个外部类注释类型,请使用`annotateClass`,并确保这个函数在你的应用程序的引导阶段执行,然后再在其他地方使用导入的类。

import { MyExternalClass } from 'external-package';
import { annotateClass } from '@deepkit/type';

interface AnnotatedClass {
    id: number;
    title: string;
}

annotateClass<AnnotatedClass>(MyExternalClass);

//all uses of MyExternalClass return now the type of AnnotatedClass
serialize<MyExternalClass>({...});

//MyExternalClass can now also be used in other types
interface User {
    id: number;
    clazz: MyExternalClass;
}

`MyExternalClass`现在可以在序列化函数和反射API中使用。

下面展示了如何注释通用类:

import { MyExternalClass } from 'external-package';
import { annotateClass } from '@deepkit/type';

class AnnotatedClass<T> {
    id!: T;
}

annotateClass(ExternalClass, AnnotatedClass);

Reflection

为了直接处理类型信息本身,这里有两个基本变种。类型对象和反思类。下面将讨论反思类。函数`typeOf`返回类型对象,它是非常简单的对象字面。它总是包含一个`kind',这是一个数字,通过枚举`ReflectionKind’获得其含义。ReflectionKind`在包@deepkit/type`中定义如下:

enum ReflectionKind {
  never,    //0
  any,     //1
  unknown, //2
  void,    //3
  object,  //4
  string,  //5
  number,  //6
  boolean, //7
  symbol,  //8
  bigint,  //9
  null,    //10
  undefined, //11

  //... and even more
}

有许多可能的类型对象可以被返回。其中最简单的是`never`、anyunknownvoid、null`和`undefined,其表示方法如下:

{kind: 0}; //never
{kind: 1}; //any
{kind: 2}; //unknown
{kind: 3}; //void
{kind: 10}; //null
{kind: 11}; //undefined

例如,数字0是`ReflectionKind`枚举的第一个条目,在这里是`never`,数字1是第二个条目,这里是`any`,以此类推。因此,原始类型如`string`、number、`boolean`被表示为:

typeOf<string>(); //{kind: 5}
typeOf<number>(); //{kind: 6}
typeOf<boolean>(); //{kind: 7}

这些相当简单的类型在类型对象处没有进一步的信息,因为它们被直接作为类型参数传递给`typeOf`。然而,如果类型是通过类型别名传递的,额外的信息可以在类型对象中找到。

type Title = string;

typeOf<Title>(); //{kind: 5, typeName: 'Title'}

在这种情况下,类型别名`title`的名字也会出现。如果一个类型别名是一个泛型,那么传递的类型也将在类型对象处可用。

type Title<T> = T extends true ? string : number;

typeOf<Title<true>>();
{kind: 5, typeName: 'Title', typeArguments: [{kind: 7}]}

如果传递的类型是一个索引访问操作的结果,那么容器和索引类型将出现:

interface User {
  id: number;
  username: string;
}

typeOf<User['username']>();
{kind: 5, indexAccessOrigin: {
    container: {kind: Reflection.objectLiteral, types: [...]},
    Index: {kind: Reflection.literal, literal: 'username'}
}}

接口和对象字面都作为Reflection.objectLiteral输出,并包含`types`数组中的属性和方法。

interface User {
  id: number;
  username: string;
  login(password: string): void;
}

typeOf<User>();
{
  kind: Reflection.objectLiteral,
  types: [
    {kind: Reflection.propertySignature, name: 'id', type: {kind: 6}},
    {kind: Reflection.propertySignature, name: 'username',
     type: {kind: 5}},
    {kind: Reflection.methodSignature, name: 'login', parameters: [
      {kind: Reflection.parameter, name: 'password', type: {kind: 5}}
    ], return: {kind: 3}},
  ]
}

type User  = {
  id: number;
  username: string;
  login(password: string): void;
}
typeOf<User>(); //returns the same object as above

索引签名也在 "types "数组中。

interface BagOfNumbers {
    [name: string]: number;
}


typeOf<BagOfNumbers>;
{
  kind: Reflection.objectLiteral,
  types: [
    {
      kind: Reflection.indexSignature,
      index: {kind: 5}, //string
      type: {kind: 6}, //number
    }
  ]
}

type BagOfNumbers  = {
    [name: string]: number;
}
typeOf<BagOfNumbers>(); //returns the same object as above

类类似于对象字面,除了 "classType "是对类本身的引用外,它们的属性和方法也在 "types "数组中。

class User {
  id: number = 0;
  username: string = '';
  login(password: string): void {
     //do nothing
  }
}

typeOf<User>();
{
  kind: Reflection.class,
  classType: User,
  types: [
    {kind: Reflection.property, name: 'id', type: {kind: 6}},
    {kind: Reflection.property, name: 'username',
     type: {kind: 5}},
    {kind: Reflection.method, name: 'login', parameters: [
      {kind: Reflection.parameter, name: 'password', type: {kind: 5}}
    ], return: {kind: 3}},
  ]
}

注意,类型已经从Reflection.propertySignature变为Reflection.property,Reflection.methodSignature变为Reflection.method。由于类上的属性和方法有额外的属性,这些信息也可以被检索到。后者还包括`visibility`、abstract`和`default。 类的类型对象只包括类本身的属性和方法,不包括超类的属性和方法。这与接口/对象文字的类型对象相反,后者将所有父类的所有属性签名和方法签名解析为 "类型"。要解决超类的属性和方法,可以使用ReflectionClass及其`ReflectionClass.getProperties()(见下面的章节)或@deepkit/type`的`resolveTypeMembers()`。

有一大堆类型对象。例如,对于字面意思、模板字面意思、承诺、枚举、联合、数组、元组等等。要想知道哪些类型都存在,有哪些信息,建议从`@deepkit/type`中导入`type`。它是一个具有所有可能的子类型的 "union",如TypeAny、TypeUnknonwn、TypeVoid、TypeString、TypeNumber、TypeObjectLiteral、TypeArray、TypeClass,以及更多。确切的结构可以在那里找到。

类型缓存

一旦没有传递通用参数,类型对象就会被缓存到类型别名、函数和类。具体来说,这意味着对`typeOf<MyClass>()`的调用将总是返回相同的对象。

type MyType = string;

typeOf<MyType>() === typeOf<MyType>(); //true

但是只要使用Generic类型,新的对象将总是被创建,即使传递的类型总是相同。这是因为理论上有无限多的组合是可能的,所以缓存实际上是一种内存泄漏。

type MyType<T> = T;

typeOf<MyType<string>>() === typeOf<MyType<string>>();
//false

然而,只要一个类型被多次实例化为递归类型,它就会被缓存起来。然而,缓存的持续时间只限于计算类型的时刻,此后就不存在了。另外,虽然Type对象被缓存了,但返回的是一个新的引用,并不是完全相同的对象。

type MyType<T> = T;
type Object = {
   a: MyType<string>;
   b: MyType<string>;
};

typeOf<Object>();

`MyType<string>`只要`Object`被计算,就被缓存。因此,`a’和`b’的PropertySignature从缓存中具有相同的`类型',但不是同一个Type对象。

所有非根Type对象都有一个父属性,它通常指向包围的父。

type ID = string | number;

typeOf<ID>();
*Ref 1* {
  kind: ReflectionKind.union,
  types: [
    {kind: ReflectionKind.string, parent: *Ref 1* } }
    {kind: ReflectionKind.number, parent: *Ref 1* }
  ]
}

`Ref 1`指向实际的联合类型对象。

在缓存类型对象的情况下,如上所述,`parent`属性并不总是真正的父对象。所以,例如,对于一个多次使用的类,虽然`types`中的即时类型(TypePropertySignature和TypeMethodSignature)指向正确的TypeClass,但这些签名类型的`type`指向缓存条目的TypeClass的签名类型。知道这一点很重要,这样就不会无限地读取父结构,而只读取直接的父结构。父类没有无限精度是由于性能的原因。

JIT缓存

在进一步的课程中,描述了一些经常基于Type对象的函数和特性。为了以一种高性能的方式实现其中的一些内容,需要对每个类型对象进行JIT缓存(just in time)。这可以通过`getJitContainer(type)`提供。这个函数返回一个简单的对象,上面可以存储任何数据。只要不保留对该对象的引用,只要Type对象本身也不再被引用,它就会通过GC自动删除自己。

反射类

除了`typeOf<>()`函数,还有各种反射类,它们提供了Type对象的OOP替代品。反射类只适用于类、界面/对象字面和函数以及它们的直接子类型(属性、方法、参数)。所有更深层次的类型必须再次用Type对象来读取。

import { ReflectionClass } from '@deepkit/type';

interface User {
    id: number;
    username: string;
}


const reflection = ReflectionClass.from<User>();

reflection.getProperties(); //[ReflectionProperty, ReflectionProperty]
reflection.getProperty('id'); //ReflectionProperty

reflection.getProperty('id').name; //'id'
reflection.getProperty('id').type; //{kind: ReflectionKind.number}
reflection.getProperty('id').isOptional(); //false

接收类型信息

为了提供自己对类型进行操作的函数,提供给用户手动传递一个类型可能是有用的。例如,在一个验证函数中,提供要请求的类型作为第一个类型参数和要验证的数据作为第一个函数参数可能是有用的。

validate<string>(1234);

为了让这个函数接收类型`string`,它必须告诉类型编译器这一点。

function validate<T>(data: any, type?: ReceiveType<T>): void;

`ReceiveType`与第一个类型参数`T`的引用给类型编译器发出信号,任何对`validate`的调用应该把类型放在第二位(因为`type`被声明在第二位)。为了在运行时读出信息,使用了`resolveReceiveType`函数。

import { resolveReceiveType, ReceiveType } from '@deepkit/type';

function validate<T>(data: any, type?: ReceiveType<T>): void {
    type = resolveReceiveType(type);
}

将结果分配给同一个变量是很有用的,这样就不会不必要地创造一个新的变量。在`type`中,要么现在就存储一个类型对象,要么就抛出一个错误,例如,没有传递类型参数,Deepkit的类型编译器没有正确安装,或者没有启用类型信息的发射(见上面的安装部分)。

Bytecode

为了详细了解Deepkit如何编码和读取JavaScript中的类型信息,本章的目的是。它解释了类型实际上是如何被转换为字节码的,在JavaScript中发出,然后在运行时解释。

类型编译器

类型编译器(在@deepkit/type-compiler中)负责读取TypeScript文件中定义的类型并将其编译为字节码。这个字节码拥有在运行时执行类型所需的一切。 在写这篇文章时,类型编译器是一个所谓的TypeScript转化器。这个转化器是TypeScript编译器本身的一个插件,可以将TypeScript AST(抽象语法树)转化为另一个TypeScript AST。在这个过程中,Deepkit的类型编译器会读取AST,产生相关的字节码,并将其插入AST中。

TypeScript本身不允许你通过tsconfig.json来配置这个插件,又称转化器。要么直接使用TypeScript编译器API,要么使用Webpack这样的构建系统与`ts-loader`。为了避免 Deepkit 用户的这种不便,在安装 @deepkit/type-compiler 时,Deepkit 类型编译器会自动安装在 node_modules/typescript 中。这使得所有访问本地安装的TypeScript(即`node_modules/typescript`中的那个)的构建工具都能自动启用类型编译器。这使得tsc、Angular、webpack、ts-node和其他一些工具能够自动与Deepkit的类型编译器一起工作。

如果没有启用NPM安装脚本的自动运行,因此本地安装的类型脚本不会被修改,如果你想,这个过程必须手动运行。另外,类型编译器也可以在webpack等构建工具中手动使用。

字节码编码

字节码是虚拟机的命令序列,在JavaScript中被编码为引用数组和字符串(实际的字节码)。

//TypeScript
type TypeA = string;

//generated JavaScript
const typeA = ['&'];

现有的命令本身每个都是一个字节大小,可以在`@deepkit/type-spec`中找到,作为`ReflectionOp`的枚举。在写这篇文章的时候,命令集的规模超过81条。

enum ReflectionOp {
    never,
    any,
    unknown,
    void,
    object,

    string,
    number,

    //...many more
}

一连串的命令被编码为一个字符串,以节省内存。因此,一个类型`string[]被概念化为一个字节码Program[string, array],它有字节[5, 37]`,并使用以下算法进行编码:

function encodeOps(ops: ReflectionOp[]): string {
    return ops.map(v => String.fromCharCode(v + 33)).join('');
}

据此,5成为`&`字符,37成为`F`字符。

//TypeScript
export type TypeA = string[];

//generated JavaScript
export const __ΩtypeA = ['&F'];

为了防止命名冲突,每个类型都有一个"__Ω "作为前缀。对于每个明确定义的、被导出的或被导出的类型所使用的类型,一个字节码会发射出JavaScript。

//TypeScript
function log(message: string): void {}

//generated JavaScript
function log(message) {}
log.__type = ['message', 'log', 'P&2!$/"'];

虚拟机

运行时虚拟机(在`@deepkit/type`类的处理器中)负责解码和执行编码的字节码。它总是返回一个类型对象,见上面的Reflection部分。