描述基本类型
const x: string = "john"; // 表示 x 必须是 string类型 , 且 x 的值不允许修改
var x: number; // 表示 x 必须是 number 类型 , 且 x 的值可以修改
let x: boolean; // 表示 x 必须是 boolean 类型 , 且 x 的值可以修改
// 还有undefined, null等类型
描述一个对象(或类的实例)
// 声明变量时就定义类型:
const x : {
label: string; // 目标必须包含 label 属性 , 且值必须是 string 类型
color?: string; // 目标包含或者不包含 color 属性均可, 如果有该属性, 值必须是 string 类型
readonly age: number; // 目标必须包含 age 属性, 且值必须是 number 类型, 该值不允许修改
add: (x:number, y:number): boolean; // 目标必须包含一个 add 属性, 且add属性值是一个函数
[propName: string]: any; // 目标可以包含除上面三个以外的任意属性
} = {
// ...
}
// 等同于
interface Person {
label: string;
color?: string;
readonly age: number;
add: (x:number, y:number): boolean;
[propName: string]: any;
}
let x:Person = {
// ...
}
// 等同于
type Person = {
label: string;
color?: string;
readonly age: number;
add: (x:number, y:number): boolean;
[propName: string]: any;
}
let x:Person = {
// ...
}
描述混合(联合)类型
ts 使用|来表示或
type p = string | number;
// -------
let x: p;
类型p表示变量x的值只能是string或者number类型
描述一个函数的参数与返回值
function add(name: string, score: number, color?: string): boolean {
return true;
}
这个函数中:
add函数调用时必须接受两个参数add函数第一个参数必须是string类型add函数第二个参数必须是number类型add函数第三个参数是可选的, 且必须是string类型add函数不允许有第四个参数add函数返回值必须是 boolean 类型
描述一个函数的实现
interface SomeFn {
(name: string, score: number): boolean;
// (source...)前面千万不能有名字,有名字就表示描述一个对象了
}
// 等同于
type SomeFn = (name: string, score: number) => boolean;
// 注意这里 boolean 前面是 => 不是 冒号
let x: SomeFn;
类型SomeFn表示:
- 函数
x的返回值必须是boolean类型 - 函数
x如果有第一个参数, 必须是string类型 - 函数
x如果有第二个参数, 必须是number类型 - 函数
x不允许有第三个参数
描述自身带有属性的函数
函数除了可以被调用, 函数自身也是可以添加属性的
使用type实现不了这个功能, 需要使用interface:
interface SomeFn {
description: string; // 表示函数 x 上可以添加description属性
(source: string, subString: string): boolean;
}
let x: SomeFn;
x.description = "这是一个函数本身属性";
类型SomeFn中的description表示:
- 函数
x可以添加description属性 - 函数
x的description属性值必须是string类型
数组
描述一个全部元素是基本类型的数组
type p = string[];
// 等同于
type p = Array<string>;
// 等同于
interface p {
[string: number]: string;
}
// 等同于
type p = {
[string: number]: string;
};
// ------------------------------
let x: p;
类型p表示:
- 变量
x必须是数组, - 变量
x的所有元素必须是 string 类型
描述一个全部元素是对象类型的数组
interface Person {
name: string;
age: number;
}
type p = Person[];
// 等同于
type p = Array<Person>;
// 等同于
interface p {
[string: number]: Person;
}
// 等同于
type p = {
[string: number]: Person;
};
// ------------------------------
let x: p;
类型p表示:
- 变量
x必须是数组, - 变量
x的所有元素必须是 Person 类型
描述一个全部元素是混合类型的数组
type StrOrNum = string | number;
type p = StrOrNum[];
// 等同于
type p = Array<StrOrNum>; // 等同于 type p = Array<string | number>;
// 等同于
interface p {
[string: number]: StrOrNum; // 等同于 [string: number]: string | number
}
// 等同于
type p = {
[string: number]: StrOrNum;
};
// ------------------------------
let x: p;
类型p表示:
- 变量
x必须是数组, - 变量
x的所有元素必须是string或number类型
描述一个只读类型的数组
type p = readonly string[];
// 等同于, 不建议使用
interface p {
readonly [string: number]: string;
}
// 等同于, 不建议使用
type p = {
readonly [string: number]: string;
};
// -----------
const x: p = ["john", "lily"];
类型p表示:
- 变量
x必须是数组, - 变量
x的所有元素必须是string类型 - 变量
x必须在声明的时候立即赋值, 且不允许修改内部元素的值
描述一个包含多种类型元素的数组(元组)
type p = [string, number, boolean];
// -------------
let x: p;
这表示一种特殊的元组类型, 它确切的知道每个元素的类型
class 相关
描述 class 中的属性
interface P1 {}
interface P2 {}
class Account {}
class User extends Account implements P1, P2 {
id: string; // 表示属性 id 为 string 类型
displayName?: boolean; // 表示可选的属性, boolean 类型
name!: string; // 表示 name 属性在运行时一定是有值的(非null, 非undefined)
#arrtibutes: Map<any, any>; // 表示一个私有属性, 只能在类的内部访问, 实例无法访问
roles = ["user"]; // 表示一个带有默认值的属性
readonly createAt = new Date(); // 表示一个只读属性
// 下面是两种声明函数以及定义参数与返回值类型的方式
setName(name: string): void {
this.name = name;
}
verifyName = (name: string): boolean => {
return this.name === name;
};
// 下面是ts中关于函数重载的示例
// run(): void {}
// run(options: Map<any, any>): void {}
// run(options: Map<any, any>, cb: Function): void {}
// 下面是class中的getter与setter
get accountId(): string {
return "";
}
set accountId(id: string) {
this.id = id;
}
// 下面是ts中属性修饰器的示例, 修饰器仅用于ts检查
private makeRequest() {} // 私有属性, 只能在本class的实例中调用
protected handRequest() {} // 保护性属性, 只能在本class与子类的实例中调用
// 下面是静态属性与静态方法的示例
static #userCount = 0; // 只能通过类本身访问, 不能通过实例访问
static registerUser() {
// 只能通过类本身访问, 不能通过实例访问
}
static {
this.#userCount++;
// 这里是静态代码块
// 这里的代码会自动执行, 不需要等待类被实例化
}
}
const p = new User();
描述一个 class 的实现
interface Runnable {
name: string;
run(distance: string, time: number): boolean;
}
class Person implements Runnable {
name: string;
run(distance: string, time: number): boolean {
return true;
}
}
类型Runnable表示:
- 如果没有定义
name属性 ts 会抛出错误 - 如果
name属性的值不是 string 类型, ts 会抛出错误 - 如果没有定义
run属性, ts 会抛出错误 - 如果
run不是一个函数, ts 会抛出错误 - 如果
run函数中已定义的参数类型顺序未与(string, number)保持一致, ts 会抛出错误 - 如果
run函数的参数数量超过(distance: string, time: number), ts 会抛出错误 - 如果
run的返回值不是boolean类型, ts 会抛出错误
描述一个class类型的参数
interface PersonClass {
// 注意这里有一个 new 关键字, 表示该interface定义的是class本身, 而不是class的实现
new (name: string, age: number, adult: boolean);
}
function generatePerson(
p: PersonClass, // 也可以简写为 p: { new (name: string, age: number, adult: boolean)}
name: string,
age: number,
adult?: boolean
) {
// 这里要求参数 p 必须是一个class,
return new p(name, age, adult);
}
class Person {
// ...
}
generatePerson(Person, "lily", 30);
类型PersonClass表示:
- 函数
generatePerson调用时,第一个参数必须是一个class - 如果该
class内部有construct函数, 且construct有参数,那么construct的参数类型必须满足:- 如果有第一个参数, 必须是
string类型 - 如果有第二个参数, 必须是
number类型 - 如果有第三个参数, 必须是
boolean类型 - 不允许有第四个参数
- 如果有第一个参数, 必须是
类型扩展(extends与&)
interface Animal {
name: string;
}
interface Color {
color: string;
}
// interface使用extends扩展类型
interface Bear extends Animal, Color {
honey: boolean;
}
// 等同于:
type Bear = Animal &
Color & {
honey: boolean;
};
type 与 interface 的区别
interface只能用来定义{}格式的类型, 而type可以定义任何类型interface可以通过多次声明来扩展属性, 而type是不可扩展的(被 type 定义的类型是被锁死的, 不能添加新属性)- 需要使用继承时,
interface的extends比type的&在类型检查器中运行速度稍微快一些 - 相同名称的
interface在同一个作用域下会被合并, 可能导致预料之外的问题
何时用 type,何时用 interface
- 官方推荐
interface, 但个人建议使用type更合适, 因为可以避免上面的第 4 条问题 - 需要使用继承关系时, 还是推荐使用
interface
泛型
函数中的泛型
function add<T>(v: T): T[] {
return new Array(3).fill(v);
}
const x = add(30);
const y = add<string>("30");
上面的案例:
const x = add(30);中T的类型取决于add函数调用时参数v的类型, 所以返回值是number[]类型const y = add<string>("30")中T的类型已经被锁定为string类型, 所以参数v必须是string类型, 返回值是string[]类型, 否则会抛出错误
函数类型中的泛型
type Run<T> = (scores: T[]) => T;
// 等同于
interface Run<T> {
(score: T[]): T;
}
// 用法:
const x: Run<number> = function (scores: number[]): number {
return Math.max(...scores);
};
上面的案例中, type Run<T> = (scores: T[]) => T 表示:
Run类型的函数在定义时必须指定T的具体类型Run类型的函数在定义时如果有第 1 个参数, 该参数必须是T组成的数组Run类型的函数在定义时返回值必须也是T类型
x: Run<number> 表示:
- 函数
x中的T已经被锁定为number类型 - 函数
x的参数必须是number组成的数组 - 函数
x的返回值必须也是number类型
class 中的泛型
class Books<T> {
list: T[];
constructor() {}
add(v: T): T[] {
return this.list;
}
}
class Animal {} // 动物类
class Plant {} // 植物类
const x1 = new Books<Animal>();
const x2 = new Books<Plant>();
上面的案例中,
- 使用构造函数
new Book时, 必须提前指定T具体的类型(锁定泛型类型), 否则 ts 会抛出错误 new Books<Animal>()中T被锁定为Animal类型,add函数调用时, 参数也必须是Animal的实例new Books<Plant>()中T被锁定为Plant类型,add函数调用时, 参数也必须是Plant的实例
interface 中的泛型
interface MysqlOption {
// ...
url: string;
host: string;
}
interface TypeOrmOptions<T> {
options: T;
}
const x: TypeOrmOptions<MysqlOption> = {
options: {
url: "",
host: "",
},
};
上面案例中, 声明x时, 必须提前指定T具体的类型(锁定泛型类型), ts 就可以检查到options内的属性是否正确
泛型约束
函数中的泛型约束
type Base = {
length: number;
age?: number;
};
function add<T extends Base>(x: T): boolean {
return true;
}
add({ name: "john", length: 10 });
上面案例中<T extends Base>就是约束条件, 这个条件要求add函数调用时, 参数:
- 必须有
length属性, 且属性值必须是number类型 - 如果有
age属性, 属性值必须是number类型
函数类型中的泛型约束
type Logger<T extends { time: string }> = (content: T) => T;
type Log = {
msg: string;
time: string;
};
const x: Logger<Log> = function (content: Log): Log {
return {} as Log;
};
上面的案例中, T extends { time: string }就是约束条件, 整个语句表示:
- 函数
x的参数必须包含time属性. - 函数
x的参数中的time属性值必须是string类型 - 函数
x的参数被锁定为Log类型 - 函数
x的返回值必须是Log类型
keyof 运算符
使用 keyof 从已知类型中获取新的类型
type P1 = { x: string; y: number };
type P2 = keyof P1; // 相当于`type P2 = string | number`
type P1 = { [x: number]: any };
type P2 = keyof P1; // 相当于`type P2 = number`
type P1 = { [x: string]: any };
type P2 = keyof P1; // 相当于`type P2 = string | number` , 因为在JS中 obj[0]和obj['0']是一样的
使用 keyof 从指定对象中获取新的类型
const person = {
name: "320",
age: 20,
goods: [],
};
type p = typeof person;
// `typeof` 从person中自动推断类型
// 相当于
type p = {
name: string;
age: number;
goods: any[];
};
使用 ReturnType 从函数中获取新的类型
type Predicate = (x: unknown) => boolean;
type K = ReturnType<Predicate>; // 相当于 type K = boolean
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;
// ReturnType<typeof f> 从函数f中自动推断类型
// 相当于
type P = { x: number; y: number };
从已知类型中映射新的类型 1
先来个简单的:
type p1 = {
name: string;
age: number;
goods: string[];
};
type MapType<t> = {
[prop in keyof t]: string;
};
// MapType 可以看做是声明了一个函数
// t 可以看做是一个参数,可以是任意字符, 这个参数必须是一个ts类型
// [prop in keyof t] 可以看做类似于JS中的 (k in obj), prop可以是任意字符
type p2 = MapType<p1>;
// 相当于
type p2 = {
name: string;
age: string;
goods: string;
};
上面案例中, p2以p1为基础, 把所有属性的类型都改为string
从已知类型中映射新的类型 2
type p1 = {
name: string;
age: number;
goods: string[];
};
type MapType<t> = {
[prop in keyof t]: (param: t[prop]) => t[prop];
};
// param 可以看做是自定义的形参, 可以是任意字符
type p2 = MapType<p1>;
// 相当于
type p2 = {
name: (param: string) => string;
age: (param: number) => number;
goods: (param: string[]) => string[];
};
上面案例中, p2以p1为基础:
name的类型从string改为返回string类型的函数age的类型从number改为返回number类型的函数goods的类型从string[]改为返回string[]类型的函数
使用条件表达式获取类型
type ConditionMap<t> = t extends { length: number } ? t : never;
type p1 = ConditionMap<{ name: string }>;
// 相当于
type p1 = never;
// ------------------
type p2 = ConditionMap<{ name: string; length: number }>;
// 相当于
type p2 = {
name: string;
length: number;
};
上面的案例中:
ConditionMap可以看做是声明了一个函数t可以看作是函数的参数, 但是这个参数必须是一个 ts 类型t extends {length: number}用来判断参数t中是否包含length:number属性- 如果满足上一条, 返回
t本身, 否则返回never
模板文字类型
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
类型AllLocaleIDs相当于:
type AllLocaleIDs =
| "welcome_email_id"
| "email_heading_id"
| "footer_title_id"
| "footer_sendoff_id";
内置字符串运算符
ts 内置了 4 个字符串运算符Uppercase Lowercase Captialize Uncaptialize:
type p1 = Uppercase<"Id" | "Name">; // 全部转为大写, 相当于 p1 = "ID" | "NAME"
type p2 = Lowercase<"Id" | "Name">; // 全部转为小写, 相当于 type p2 = "id" | "name"
type p3 = Capitalize<"id" | "name">; // 首字母转为大写, 相当于 type p3 = "Id" | "Name"
type p4 = Uncapitalize<"Id" | "Name">; // 取消首字母大写, 相当于 type p4 = "id" | "name"
自定义字符串运算符
// 这里的 Str 相当于一个参数
type PreTable<Str extends string> = `user_${Str}`;
type p = PreTable<"id" | "name">; // 相当于 type p = "user_id" | "user_name"
namespace
主要目的是帮助组织和封装代码,以避免全局命名冲突,并实现一种逻辑上的代码分组
namespace N1 {
export type F1 = (v: string) => void;
}
namespace N1 {
export type F2 = (v: string) => void;
}
const x: N1.F1 = (v: string) => {};
上面的案例中:
- 命名空间
N1可以跨文件定义, 类似于interface可以被跨文件扩展属性 - 通过
.访问命名空间内的某个类型
declare 声明
本节中的
index.d.ts一定要添加到tsconfig.json.includes选项中, 否则不会生效
{
"files": [
"main.ts",
],
"include": [
"index.d.ts",
],
}
给全局属性添加 ts 支持
假设有以下代码:
// main.ts
function foo() {
console.log(GlobalEnv.foo);
}
GlobalEnv是一个全局属性, 如果没有声明, ts 会抛出错误
可以在项目的 ts 声明文件index.d.ts中添加如下代码:
// types/index.d.ts
declare var GlobalEnv: {
foo: string;
};
给没有类型说明文件的第三方模块添加类型说明
假设有一个foo模块:
function init() {
console.log("init");
}
exports.init = init;
在 ts 文件中使用:
import foo from "foo"; // TS会提示: 文件“...node_modules/foo/index.d.ts”不是模块
foo.init();
可以在项目的 ts 声明文件index.d.ts中添加如下代码:
declare module "foo" {
export function init(): void;
}
给已有类型说明文件的第三方模块增加类型
假设有一个模块foo
// node_modules/foo/index.js
function init() {
console.log("init");
}
exports.init = init;
// node_modules/foo.index.d.ts
export function init(): void;
在 ts 文件中使用:
import foo from "foo";
foo.init();
foo.add(); // TS会提示: 类型“typeof import("foo")”上不存在属性“add”。
可以在项目的 ts 声明文件index.d.ts中添加如下代码:
declare module "foo" {
export function add(): void;
}
给已有类型说明文件的第三方模块中的某个类型扩展新的属性
假设有一个模块foo
// node_modules/foo/index.js
function init(options) {
console.log("init", options);
}
exports.init = init;
// node_modules/foo.index.d.ts
export interface OrmOptions {
name: string;
url: string;
port: number;
}
export function init(v: OrmOptions): void;
在 ts 文件中使用:
import foo from "foo";
const x: foo.OrmOptions = {
name: "mysql",
url: "loclhost",
port: 3636,
driver: "mysql2",
// TS提示1: 不能将类型“{ name: string; url: string; port: number; driver: string; }”分配给类型“OrmOptions”。
// TS提示2: 对象字面量只能指定已知属性,并且“driver”不在类型“OrmOptions”中
};
init(option);
可以在项目的 ts 声明文件index.d.ts中添加如下代码, 用来扩展OrmOptions:
import foo from "foo"; // 一定要有这句!
declare module "foo" {
export interface OrmOptions {
driver: string;
}
}