专栏首页终身学习者【TS 演化史 -- 17】各文件的JSX工厂 、有条件类型和映射类型修饰符

【TS 演化史 -- 17】各文件的JSX工厂 、有条件类型和映射类型修饰符

作者:Marius Schulz 译者:前端小智 来源:https://mariusschulz.com/

各文件的JSX工厂

TypeScript 2.8允许咱们在每个文件的基础上指定JSX工厂名。在早期版本,只能通过--jsxFactory编译器选项指定JSX工厂名。此设置适用于整个项目中的每个JSX文件。现在,咱们还可以通过在文件的开头添加一个特殊的@jsx注释来覆盖项目范围的--jsxFactory设置。

假设咱们要使用Preact渲染字符串 "Hello World!" 并放入<div id="app">容器中。 Preact 使用h函数来创建 JSX 元素。 咱们可以在.tsx文件的开头添加特殊的/ ** @jsx h */注释(也称为“pragma”):

有了/** @jsx h */编译指示后,编译器将为上述文件生成以下 JS 代码:

/** @jsx h */
import { h, render } from "preact";
render(
  h("h1", null, "Hello World!"),
  document.getElementById("app")
);

下面是用来编译代码的tsconfig.json配置文件:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "jsx": "react",
    "strict": true
  }
}

请注意,如果您使用/** ... */块注释语法,则编译器仅识别编译指示。 如果使用// ...单行注释语法,则不会更改JSX出厂设置。

什么是JSX工厂

JSX不是 ECMAScript 标准的一部分;也就是说,它本身不是有效的 JS。因此,包含JSX的脚本或模块不能直接在浏览器中运行。与带有类型注释的文件一样,JSX 文件首先需要编译成纯 JS 文件。--jsxFactory选项告诉 TypeScript 编译器应该如何编译JSX元素。

注意 <h1> Hello World!</h1>如何转换为 h("h1", null, "Hello World!")Preact 使用函数h创建虚拟 DOM 元素,这就是为什么咱们将h指定为JSX工厂名称的原因。 我们还需要从preact包中导入h,以便它在模块中可用。

指定每个文件和每个项目的JSX工厂

那么,什么时候需要在每个文件的基础上指定JSX工厂呢?如果咱们在项目中只将JSX与单个 JS库一起使用,则不需要对每个文件进行配置。在这种情况下,更容易在tsconfig中更改--jsxFactory选项。这样它就可以应用于项目中的所有JSX文件:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "jsx": "react",
    "jsxFactory": "h",
    "strict": true
  }
}

默认情况下,使用--jsx react选项时,--jsxFactory选项设置为React.createElement。 因此,如果咱们使用的是 React,则完全不需要指定--jsxFactory选项,也不必添加/ ** @jsx ... * /编译指示。

如果在同一项目中将多个JS库与JSX一起使用,则JSX工厂的按文件配置很有用。 例如,咱们可能想将Vue组件添加到主要用 eact 编写的Web应用程序中。 / ** @jsx ... * / 编译指示允许咱们为这些文件指定不同的 JSX 工厂,而不必具有多个tsconfig.json文件。

有条件类型

TypeScript 2.8 引入了有条件类型,这是类型系统的强大而令人兴奋的补充。 有条件类型使咱们可以表达非均匀类型映射,即,根据条件而不同的类型转换。

有条件的类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:

T extends U ? X : Y

上面的类型意思是,若T能够赋值给U,那么类型是X,否则为Y

下面是一个在 TypeScript 的lib.es5.d.ts类型定义文件中预定义的有条件类型的例子

/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T extends null | undefined ? never : T;

如果类型T可赋值给类型null或类型undefined,则NonNullable<T>类型为never类型;否则它将保留类型 Tnever类型是 TypeScript 的底层类型,表示从未出现的值的类型。

分布式有条件类型

那么,为什么e 条件类型和never类型的组合是有用的呢?它有效地允许咱们从联合类型中删除组成类型。如果有条件类型里待检查的类型是naked type parameter,那么它也被称为“分布式有条件类型”。 分布式有条件类型在实例化时会自动分发成联合类型。 例如,实例化T extends U ? X : YT的类型为A | B | C,会被解析为(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

这个描述相当抽象,咱们来看一个具体的例子。咱们定义一个EmailAddress类型别名,它表示四种不同类型的联合,包括nullundefined类型

type EmailAddress = string | string[] | null | undefined;

现在,咱们将NonNullable<T>类型应用于EmailAddress,并逐步解析结果类型:

type NonNullableEmailAddress = NonNullable<EmailAddress>;

咱们首先用别名的联合类型替换EmailAddress

type NonNullableEmailAddress = NonNullable<
  | string
  | string[]
  | null
  | undefined
>;

这就是有条件类型的分配特性发挥作用的地方。NonNullable<T>类型应用于联合类型,这相当于将有条件类型应用于联合类型中的所有类型:

type NonNullableEmailAddress =
  | NonNullable<string>
  | NonNullable<string[]>
  | NonNullable<null>
  | NonNullable<undefined>;

现在,咱们可以在任何地方通过其定​​义替换NonNullable<T>

type NonNullableEmailAddress =
  | (string extends null | undefined ? never : string)
  | (string[] extends null | undefined ? never : string[])
  | (null extends null | undefined ? never : null)
  | (undefined extends null | undefined ? never : undefined);

接下来,咱们必须解析这四种有条件类型。stringstring[]都不能赋值给 null | undefined,这就是前两种类型选择stringstring[]的原因。nullundefined都可以赋值给null | undefined,这就是为什么后两种类型都选择never:

type NonNullableEmailAddress =
  | string
  | string[]
  | never
  | never;

因为never是每个类型的子类型,所以可以从联合类型中省略它:

type NonNullableEmailAddress = string | string[];

这就是我们期望的类型。

使用有条件类型的映射类型

现在让咱们看一个更复杂的例子,它将映射类型与条件类型组合在一起。这里,我们定义了一个类型,它从一个类型中提取所有不可为空的属性键

type NonNullablePropertyKeys<T> = {
  [P in keyof T]: null extends T[P] ? never : P
}[keyof T];

这种类型乍一看似乎相当神秘。再一次,将通过查看一个具体的示例并逐步解析得到的类型来尝试揭开它的神秘面纱。

假设咱们有一个User类型,想要使用NonNullablePropertyKeys<T>类型来找出哪些属性是不可空的:

type User = {
  name: string;
  email: string | null;
};

type NonNullableUserPropertyKeys = NonNullablePropertyKeys<User>;

下面是咱们如何解析NonNullablePropertyKeys<User>。首先,咱们将User类型作为T类型参数的类型参数提供:

type NonNullableUserPropertyKeys = {
  [P in keyof User]: null extends User[P] ? never : P
}[keyof User];

其次,咱们在映射类型中解析keyof UserUser类型有两个属性,nameemail,因此咱们最终得到一个带有“name”“email”字符串字面量类型的联合类型:

type NonNullableUserPropertyKeys = {
  [P in "name" | "email"]: null extends User[P] ? never : P
}[keyof User];

接下来,我们将在映射中展开P in...,并将P类型替换为“name”“email”

type NonNullableUserPropertyKeys = {
  name: null extends User["name"] ? never : "name";
  email: null extends User["email"] ? never : "email";
}[keyof User];

然后,通过查找User中的nameemail属性的类型,咱们可以继续并解析索引访问类型User["name"]User["email"]

type NonNullableUserPropertyKeys = {
  name: null extends string ? never : "name";
  email: null extends string | null ? never : "email";
}[keyof User];

现在是应用条件类型的时候了。null不扩string,但它确实扩展了string | null,因此咱们分别以“name”never类型结束:

type NonNullableUserPropertyKeys = {
  name: "name";
  email: never;
}[keyof User];

现在咱们已经完成了映射类型和条件类型,再一次,咱们将解析keyof Use

type NonNullableUserPropertyKeys = {
  name: "name";
  email: never;
}["name" | "email"];

咱们现在有一个索引访问类型,它查找nameemail属性的类型。TypeScript 通过逐个查找每个类型并创建联合类型来解决这个问题:

type NonNullableUserPropertyKeys =
  | { name: "name"; email: never }["name"]
  | { name: "name"; email: never }["email"];

现在,咱们可以在两个对象类型中查找nameemail属性。name属性的类型是“name”,而email属性的类型是“never”

type NonNullableUserPropertyKeys =
  | "name"
  | never;

和前面一样,咱们可以通过清除never类型来简化生成的联合类型:

type NonNullableUserPropertyKeys = "name";

User类型中唯一不可为空的属性键是“name”

咱们进一步研究这个示例,并定义一个类型来提取给定类型的所有不可空属性。我们可以将Pick <T,K>类型用于lib.es5.d.ts中预定义的类型:

/**
 * From T, pick a set of properties
 * whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

咱们可以结合NonNullablePropertyKeys<T>类型和Pick<T, K>来定义NonNullableProperties<T>,这是咱们要查找的类型:

type NonNullableProperties<T> = Pick<T, NonNullablePropertyKeys<T>>;

type NonNullableUserProperties = NonNullableProperties<User>;
// { name: string }

实际上,这是咱们期望的类型:在User类型中,只有name属性不可空。

有条件类型中的类型推断

有条件类型支持的另一个有用特性是使用新的infer关键字推断类型变量。在有条件类型的extends子句中,可以使用新的infer关键字来推断类型变量,从而有效地执行类型上的模式匹配

type First<T> =
  T extends [infer U, ...unknown[]]
    ? U
    : never;

type SomeTupleType = [string, number, boolean];
type FirstElementType = First<SomeTupleType>; // string

注意,推断的类型变量(在本例中为U)只能在条件类型的true分支中使用。

TypeScript 一个长期存在的特性要求是能够提取给定函数的返回类型。下面是ReturnType<T>类型的简化版本,该类型是在lib.es5.d.ts中预定义的。它使用infer关键字来推断函数类型的返回类型:

type ReturnType<T> =
  T extends (...args: any[]) => infer R
    ? R
    : any;

type A = ReturnType<() => string>;         // string
type B = ReturnType<() => () => any[]>;    // () => any[]
type C = ReturnType<typeof Math.random>;   // number
type D = ReturnType<typeof Array.isArray>; // boolean

注意,咱们必须使用typeof来获得Math.random()Array.isArray()方法的返回类型。咱们需要传递类型作为类型参数T的参数,而不是值;这就是为什么ReturnType<Math.random>ReturnType<Array.isArray>是不正确的。

预定义的有条件类型

TypeScript 2.8 在lib.d.ts里增加了一些预定义的有条件类型:

  • Exclude<T, U> -- 从T中剔除可以赋值给U的类型。
  • Extract<T, U> -- 提取T中可以赋值给U的类型。
  • NonNullable<T> -- 从T中剔除null和undefined。
  • ReturnType<T> -- 获取函数返回值类型。
  • InstanceType<T> -- 获取构造函数类型的实例类型。

Exclude<T, U>

Exclude<T, U>T中剔除可以赋值给U的类型。

定义:

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

事例:

type A = Exclude<string | string[], any[]>;      // string
type B = Exclude<(() => void) | null, Function>; // null
type C = Exclude<200 | 400, 200 | 201>;          // 400
type D = Exclude<number, boolean>;               // number

Extract<T, U>

通过Extract <T,U>类型,提取T中可以赋值给U的类型。

定义:

/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;

事例:

type A = Extract<string | string[], any[]>;      // string[]
type B = Extract<(() => void) | null, Function>; // () => void
type C = Extract<200 | 400, 200 | 201>;          // 200
type D = Extract<number, boolean>;               // never

NonNullable<T>

咱们上面使用了NonNullable<T>类型,该类型从T中过滤出nullundefined类型。

定义:

/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T extends null | undefined ? never : T;

事例:

type A = NonNullable<boolean>;            // boolean
type B = NonNullable<number | null>;      // number
type C = NonNullable<string | undefined>; // string
type D = NonNullable<null | undefined>;   // never

注意,空类型D是如何由never表示的。

ReturnType<T>

ReturnType<T> 获取函数返回值类型。

定义:

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any[]) => any> =
  T extends (...args: any[]) => infer R
    ? R
    : any;

事例:

type A = ReturnType<() => string>;         // string
type B = ReturnType<() => () => any[]>;    // () => any[]
type C = ReturnType<typeof Math.random>;   // number
type D = ReturnType<typeof Array.isArray>; // boolean

Parameters<T>

Parameters<T> 获取构造函数类型的实例类型。

定义:

/**
 * Obtain the parameters of a function type in a tuple
 */
type Parameters<T extends (...args: any[]) => any> =
  T extends (...args: infer P) => any
    ? P
    : never;

注意,Parameters<T>类型在结构上与ReturnType<T>类型几乎相同。 主要区别在于infer关键字的位置。

事例:

type A = Parameters<() => void>;           // []
type B = Parameters<typeof Array.isArray>; // [any]
type C = Parameters<typeof parseInt>;      // [string, (number | undefined)?]
type D = Parameters<typeof Math.max>;      // number[]

Array.isArray() 方法恰好需要一个任意类型的参数。 这就是为什么将B类型解析为[any],即具有一个元素的元组的原因。 另一方面,Math.max() 方法期望任意多个数值参数(而不是单个数组参数);因此,类型D被解析为number[](而不是[number []])。

InstanceType<T>

InstanceType<T>类型提取构造函数类型的返回类型,它相当于构造函数的ReturnType<T>

定义

/**
 * Obtain the return type of a constructor function type
 */
type InstanceType<T extends new (...args: any[]) => any> =
  T extends new (...args: any[]) => infer R
    ? R
    : any;

再次注意InstanceType <T>类型在结构上与ReturnType <T>ConstructorParameters <T>类型非常相似。

事例:

type A = InstanceType<ErrorConstructor>;    // Error
type B = InstanceType<FunctionConstructor>; // Function
type C = InstanceType<RegExpConstructor>;   // RegExp

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文: https://mariusschulz.com/blog... https://mariusschulz.com/blog... https://mariusschulz.com/blog...

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JavaScript中关于null的一切

    JavaScript有2种类型:基本类型(string, booleans number, symbol)和对象。

    前端小智@大迁世界
  • 新手都能看得懂的 ES6 Iterators

    本文旨在分析理解 Iterators。 Iterators 是 JS中的新方法,可以用来循环任意集合。 在ES6中登场的Iterators。因其可被广泛使用,并...

    前端小智@大迁世界
  • JavaScript 中如何判断变量是否为数字

    JavaScript 是一种动态类型语言,这意味着解释器在运行时确定变量的类型。实际上,这也允许我们在相同的代码中使用相同的变量来存储不同类型的数据。如果没有文...

    前端小智@大迁世界
  • 分布式中Redis实现Session终结篇

      上一篇使用Redis实现Session共享方式虽然可行,但是实际操作起来却很麻烦,现有代码已经是这个样子了,总不可能全部换掉吧!好吧,这是个很实际的问题,那...

    用户1168362
  • Druid 0.17 入门(3)—— 数据接入指南

    在快速开始中,我们演示了接入本地示例数据方式,但Druid其实支持非常丰富的数据接入方式。比如批处理数据的接入和实时流数据的接入。本文我们将介绍这几种数据接入方...

    实时计算
  • Opaque Types 之 some

    在看some之前我们肯定要提一个沸沸扬扬的主角SwitUI--这个OS实时预览的大神。里面可谓是打出透露这some这个人民币玩家……说的有点偏题啦。先来看个小例...

    大话swift
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型

    2018-09-01 08:28

    walterlv
  • Java 编译器代码定义的 Java语言的类型 Types

    一个会写诗的程序员
  • 一个简单的Windows Socket可复用框架

    一个简单的Windows Socket可复用框架 说起网络编程,无非是建立连接,发送数据,接收数据,关闭连接。曾经学习网络编程的时候用Java写了一些小的聊天程...

    Florian
  • 【Nature重磅】扩散型忆阻器带来类脑计算大突破,或成神经计算机时代“晶体管”

    【新智元导读】马萨诸塞大学阿默斯特分校研究人员研发出一种新型忆阻器,能够忠实模拟生物神经元突触的功能,相关论文日前在《自然-材料》发表。实验证明,与传统的漂移型...

    新智元

扫码关注云+社区

领取腾讯云代金券