基本类型
空集的表示
在前文中我们说类型是一种集合,对于集合来说,会存在空集的概念。例如在函数返回值中,我们可能需要对空集合进行表达。那么怎么定义空集合这一概念呢?
在 TypeScript 中空集合的概念可以借助关键字 never 表示。never 告诉我们一个函数不应该有返回值:
function raise(message: string): never {
throw new Error(`Error "${message}" raised at ${new Date()}`)
}
大多数编程语言使用 void 表示不存在有意义的值,如果将上面例子中的 never 改为 void,存在误导性。never 表示根本不返回,而 void 表示正常返回,但没有返回值。
function raise(message: string): never; // 永远不会正常返回
function raise(message: string): void; // 正常返回,但没有返回值
void 在 TypeScript 中叫做单元类型,单元类型是只有一个可能值的类型,对于这种类型来说,校验其值是没有意义的。所以当函数的结果没有意义,我们使用 void 单元类型。
自定义 Unit 类型
虽然 TypeScript 内置了 void 作为单元类型,但有时我们需要自定义一个更明确的 Unit 类型。实现 Unit 类型有多种方式:
方式一:使用 unique symbol + 私有构造函数
这种方式通过 unique symbol 确保类型唯一性,并通过私有构造函数保证只能创建一个实例:
declare const UnitType: unique symbol;
class Unit {
[UnitType]: void;
static readonly value: Unit = new Unit();
private constructor() { };
}
function greet(): Unit {
console.log("Hello world!");
return Unit.value;
}
unique symbol 属性确保相似形状的类型不会被解释为 Unit,私有构造函数确保其他代码不能实例化这个类型,静态只读属性是唯一能够创建的 Unit 实例。
方式二:直接使用 symbol 值
更简单的方式是直接使用一个 symbol 值作为 Unit 类型的唯一值:
const Unit = Symbol('Unit');
type Unit = typeof Unit;
function greet(): Unit {
console.log("Hello world!");
return Unit;
}
这种方式更简洁,通过 typeof 操作符从值推导出类型。函数总是返回同一个 symbol 值,等效于返回 void。注意这里 Unit 既是值(symbol 常量)又是类型(通过 typeof 推导)。
方式三:使用字面量类型
也可以使用一个字面量类型来实现 Unit:
type Unit = typeof undefined; // 或者 typeof null
function greet(): Unit {
console.log("Hello world!");
return undefined;
}
不过这种方式语义上不如前两种清晰,因为 undefined 和 null 在 TypeScript 中有特定的含义。
逻辑和短路
declare 关键字
declare 关键字用于告知编译器”实体已经存在,不用在编译输出中生成它的代码”。declare 用于类型声明而非实现声明。
declare 主要在如下场景中使用:声明全局变量、声明第三方库、扩展已有类型定义、声明环境模块。
在进行前后端衔接的时候,服务端可能会在 html 中加入一些全局变量,在 ts 文件直接使用会报错。
declare const myGlobalVariable: string
在开发过程中,可能需要给 window 对象增加一些属性或方法,这时需要通过 declare 对 window 对象类型进行扩展:
declare global {
interface Window {
customProperty: string
}
}
当项目中通过 CDN 或引入无类型的第三方库,可以通过 declare 对第三方库进行类型定义:
declare module "untyped-library" {
export function doSomething(input: string): number
export const version: string
}
import {doSomething, version} from 'untyped-library'
在 webpack 等构建工具中,支持引入图片、样式等非 JS 文件。这时候直接引入会出现错误:
declare module "*.png" {
const content: any
export default content
}
declare module "*.css" {
const styles: { [className: string]: string };
export default styles;
}
declare 声明的类型,通常情况下放到 global.d.ts 文件中,然后在 tsconfig.json 中进行配置:
{
"compilerOptions": {
"typeRoots": ["./global.d.ts", "./node_modules"]
}
}
typeRoots 用于指定类型查找的目录,types 选项指定加载哪些类型,粒度控制更细。
当两个配置项都不包含时,加载如下:
node_modules/
├── @types/
│ ├── node/ # 自动包含 ✓
│ ├── react/ # 自动包含 ✓
│ └── jest/ # 自动包含 ✓
├── lodash/ # 如果有自己的类型,也会包含
└── my-package/
└── index.d.ts # 自动包含 ✓
设置 typeRoots 后:
{
"compilerOptions": {
"typeRoots": ["./custom-types", "./node_modules/@types"]
}
}
查找顺序变为:
- ./custom-types/ 目录下的所有包
- ./node_modules/@types/ 目录下的所有包
- 不再自动查找其他位置
设置 types 后:
{
"compilerOptions": {
"types": ["node", "jest"]
}
}
- 只加载
@types/node和@types/jest - 忽略
@types/react、@types/lodash等其他所有类型包
同时设置 typeRoots 和 types 后:
{
"compilerOptions": {
"typeRoots": ["./custom-types"],
"types": ["my-custom-types", "node"]
}
}
- 只在
./custom-types中查找类型 - 只加载
my-custom-types和node这两个包的类型 - 完全忽略
./node_modules/@types