# TypeScript 类型系统
# 1. 基本概念
基本注解:
类型注解使用 TypeAnnotation 语法
类型声明空间中内容都可以用作注解
示例:
const num: number = 10; function identity(num: number): number { return num; }
基本类型注解:
string
/number
/boolean
示例:
let num: number; let str: string; let bool: boolean;
数组注解:
类型[]
示例:
let boolArr: boolean[];
接口注解:
合并多个类型注解
示例:
interface Name { first: string; seconde: string; } let name: Name;
内联注解:
不用给类型起名字,只用一次
示例:
let name: { first: string; seconde: string; };
特殊类型:
any
: 关闭类型检查,可赋值给任意类型的变量null
/undefined
: 可赋值给任意类型的变量void
: 表示函数没有返回值
泛型:
示例:
function reserve<T>(items: T[]): T[] { const toreturn = []; for (let i = items.length - 1; i >= 0; i--) { toreturn.push(items[i]); } return toreturn; } const arr = [1, 2, 3]; const reserved = reserve(arr);
联合类型注解:
多个类型中挑一个
示例:
function formatCommandLine(command: string[] | string) { // ... }
交叉类型:
两个类型的并集
示例:
// first 继承 second 的属性,同名不覆盖 function extend<T, U>(first: T, second: U): T & U { const result = {} as (T & U); for (let key in first) { (result as T)[key] = first[key]; } for (let key in second) { if (!(result as Object).hasOwnProperty(key)) { (result as U)[key] = second[key]; } } return result; } const x = extend({ a: 1, b: 2 }, { b: '2', c: '3' }); console.log(x); /* { "a": 1, "b": 2, "c": "3" } */
元组:
[类型1, 类型2, ...]
- 特殊的数组类型,相当于指定参数列表
类型别名:
给长类型起别名
示例:
type Text = string | { text: string }; type coordinates = [ number, number ]; type Callback = (date: string) => void;
# 2. JS 迁移到 TS
步骤:
- 添加 tsconfig.json 文件
- 将
.js
文件改为.ts
文件 - 在旧代码中使用类型注解,修复已识别的错误
- 为第三方 JS 库定义环境声明
减少错误:
通过 类型断言 减少错误
示例:
let foo = 123; let bar = 'a'; bar = foo as any;
第三方 JS 代码:
在
vender.d.ts
为其定义类型创建针对特定库的声明文件:
jquery.d.ts
:declare type JQuery = any; // 定义全局变量 declare var $: JQuery;
第三方 NPM 模块:
声明:
// 定义全局模块 declare module 'jquery';
使用:
import * as $ from 'jquery';
非 JS 资源:
声明:
declare module '*.css'; declare module '*.html';
使用:
import * as style from './style/style.css'; import * as app from './template/app.html';
# 3. @types
compilerOptions.typeRoots
:
- 默认情况下,所有
node_modules/@types
下的包的类型声明都会加入编译
# 4. 环境声明
为第三方库编写声明文件,通过 declare
关键字声明变量、函数、类等
# 5. 接口
interface Point {
x: number;
y: number;
}
class MyPoint implements Point {
constructor(public x: number, public y: number) {
}
}
# 6. 枚举
enum NumEnum {
first = 1, // 从 1 开始,保证都是真值
second,
third,
}
enum Weekday {
Monday = 'Monday',
Tuesday = 'Tuesday',
Wednesday = 'Wednesday',
Thursday = 'Thursday',
Friday = 'Friday',
Saturday = 'Saturday',
Sunday = 'Sunday',
}
// 给枚举添加方法
namespace Weekday {
export function isBusinessDay(day: Weekday) {
switch (day) {
case Weekday.Saturday: return false;
case Weekday.Sunday: return false;
default: return true;
}
}
}
console.log(Weekday.isBusinessDay( Weekday.Monday )); // true
console.log(Weekday.isBusinessDay( Weekday.Saturday )); // false
# 7. lib.d.ts
安装 typescript 后,会有 lib.d.ts 文件:
位置:
node_modules\typescript\lib\lib.d.ts
内容:
/// <reference lib="es5" /> /// <reference lib="dom" /> /// <reference lib="webworker.importscripts" /> /// <reference lib="scripthost" />
# 8. 函数
重载:
// 重载:
function sum(x: number): number;
function sum(x: number, y: number): number;
function sum(x: number, y: number, z: number): number;
// 实现
function sum(x: number, y?: number, z?: number) {
if (y == null) {
y = 0;
}
if (z == null) {
z = 0;
}
return x + y + z;
};
console.log(sum(1));
console.log(sum(1, 2));
console.log(sum(1, 2, 3));
声明函数:
type ISum = {
(x: number): number;
(x: number, y: number): number;
(x: number, y: number, z: number): number;
}
type ISum2 = (x: number) => number;
//--
let sum: ISum;
sum = function sum(x: number, y?: number, z?: number) {
if (y == null) {
y = 0;
}
if (z == null) {
z = 0;
}
return x + y + z;
};
console.log(sum(1));
console.log(sum(1, 2));
console.log(sum(1, 2, 3));
# 9. 可调用
接口:
interface ISum {
(x: number): number;
}
let sum1: ISum = function (x) {
return x;
};
箭头函数:
type ISum = (x: number) => number;
let sum1: ISum = function (x) {
return x;
};
可实例化:
class Person {
constructor(public name: string) {
}
sayHello() {
console.log(this.name, 'hello');
}
}
interface IPerson {
new (name: string): Person;
}
function sayHello(P: IPerson) {
new P('张三').sayHello();
}
sayHello(Person);
# 10. 类型断言
当 S 是 T 的子集,或 T 是 S 的子集时:
- S 能被断言为 T
当 S: T | U 时:
- S 能被断言为 T
双重断言:
function handler(event: Event) {
// const element = event as HTMLElement; // 错误
const element = (event as any) as HTMLElement;
}
# 11. freshness
更严格的 对象字面量类型 检查:
function logName(obj: { name?: string }) {
console.log(obj.name);
}
const person = { name: '张三', age: 18 };
logName(person);
logName({}); // ok
logName({ name: '张三', age: 18 }); // 错误:对象字面量只能指定已知属性
# 12. 类型保护
typeof:
function doSome(s: number | string) {
if (typeof s === 'number') {
// TS 推导出这个块 s 为 number 类型
s.toFixed(2);
} else {
// TS 推导出这个块 s 为 string 类型
s.substring(1, 3);
}
}
instanceof:
function doStuff(arg: Person | Point) {
if (arg instanceof Person) {
// TS 推导出这个块 arg 是 Person 类型
arg.name;
return;
}
// TS 推导出这个块 arg 是 Point 类型
arg.x;
}
in:
interface IPerson {
name: string;
age: number;
}
interface IPoint {
x: number;
}
function doStuff(arg: IPerson | IPoint) {
if ('name' in arg) {
// TS 推导出这个块 arg 是 IPerson 类型
arg.age;
} else {
// TS 推导出这个块 arg 是 IPoint 类型
arg.x;
}
}
字面量类型:
type IState = 'yes' | 'no';
function doStuff(state: IState) {
if (state == 'yes') {
} else if (state == 'no') {
}
}
null 和 undefined:
function doStuff(state?: string | null) {
if (state == null) {
// TS 推导出这个块 state 是 null 或 undefined 类型
return;
}
// TS 推导出这个块 state 是 string 类型
state.substring(1, 2);
}
自定义类型保护:
interface IPerson {
name: string;
age: number;
}
interface IPoint {
x: number;
}
function isPerson(arg: IPerson | IPoint): arg is IPerson {
return (arg as IPerson).name !== undefined;
}
function doStuff(arg: IPerson | IPoint) {
if (isPerson(arg)) {
// TS 推导出这个块 arg 是 IPerson 类型
arg.age;
} else {
// TS 推导出这个块 arg 是 IPoint 类型
arg.x;
}
}
回调函数:
declare var person: { address?: { road: string } };
function doSome(callback: Function) {
callback();
}
console.log(person.address.road); // 错误: 'person.address' is possibly 'undefined'
if (person.address) {
console.log(person.address.road);
var address = person.address;
doSome(() => {
console.log(person.address.road); // 错误: 'person.address' is possibly 'undefined'.
console.log(address.road); // ok
});
}
# 13. 字面量类型
示例:
type OneOfFive = 1 | 2 | 3 | 4 | 5;
type Bools = true | false;
type CardinalDirection = 'North' | 'East' | 'South' | 'West';
function move(distance: number, direction: CardinalDirection) {
// ...
}
move(10, 'North');
move(10, 'north'); // 错误
推断:
function sum(num: 100) {
// ...
}
const num = 100;
sum(num)
const person = { age: 100 };
sum(person.age); // 错误
const person2 = { age: 100 as 100 };
sum(person2.age); // ok
基于字符串的枚举:
function strEnum<T extends string>(arr: T[]): { [key in T]: key } {
const result = arr.reduce((res, item) => {
res[item] = item;
return res;
}, Object.create(null));
return result;
}
const Direction = strEnum(['North', 'East', 'South', 'West']);
console.log(Direction);
/*
{
"North": "North",
"East": "East",
"South": "South",
"West": "West"
}
*/
type IDirection = keyof typeof Direction; // "North" | "East" | "South" | "West"
let d: IDirection;
d = 'West';
d = Direction.West;
# 14. readonly
基本使用:
// 用于接口属性
interface IPerson {
readonly id: string;
}
// 用于对象成员属性
class Person {
readonly id = '123';
}
// 用于索引签名
interface Foo {
readonly [key: string]: string;
}
let p = new Person();
p.id = '1'; // 错误:直接修改会报错
// 间接修改(类型兼容)
function modifyId(p: { id: string }) {
p.id = '1';
}
modifyId(p);
console.log(p); // { "id": "1" }
Readonly<T>
:
interface IPerson {
name: string;
age: string;
}
// 将 IPerson 的所有属性都变为 readonly
type ReadonlyPerson = Readonly<IPerson>;
/*
type ReadonlyPerson = {
readonly name: string;
readonly age: string;
}
*/
ReadonlyArray<T>
:
let arr: ReadonlyArray<number> = [1, 2, 3];
arr[0] = 11; // 错误
arr.reverse(); // 变更数组的方法不可用
# 15. 泛型
在成员之间提供类型约束,在参数列表和返回值中同时使用才有意义
function parse<T>(name: T): T {
return name;
}
parse(1).toFixed(2);
parse('1').substring(1);
# 16. 类型推断
TS 根据一些简单的规则来推断变量的类型:
// 定义变量
// => let num: number
let num = 1;
// 函数返回值
// => function add(a: number, b: number): number
function add(a: number, b: number) {
return a + b;
}
// 赋值
type IAdd = (a: number, b: number) => number;
let foo: IAdd = (a, b) => {
a.toFixed(2); // (parameter) a: number
return a + b
};
// 解构
let person = { age: 18 };
// => let age: number
let { age } = person;
let nums = ['a', 'b'];
// => let str1: string
let [str1] = nums;
compilerOptions.noImplicitAny
:
- 类型推断为
any
的都会报错
# 17. 类型兼容性
函数-返回值:
let p2 = () => ({ x: 1, y: 2 })
let p3 = () => ({ x: 1, y: 2, z: 3 })
p2 = p3; // 正确: 多的 可以赋值给 少的
p3 = p2; // 错误: 少的 不可以赋值给 多的
函数-参数:
let p2 = (x: number, y: number) => x + y;
let p3 = (x: number, y: number, z: number) => x + y + z;
p2 = p3; // 正确: 多的 不可以赋值给 少的
p3 = p2; // 错误: 少的 可以赋值给 多的
枚举:
enum NumEnum {
x, y
}
let num = NumEnum.x;
let z = 0;
num = z;
z = num;
enum StrEnum {
a = 'a', b = 'b'
}
let str = StrEnum.a;
let c = 'c';
str = c; // 错误:string 类型不能赋值给 字符串枚举类型
c = str;
# 18. never
不能优雅退出的函数:
// 没有返回值的函数
function test():never {
while(true){}
}
// 总是抛出错误的函数
function test2():never {
throw 'xyz';
}
只有 never 类型的数据才可以赋值给 never 类型的数据:
let x: never = test();
# 19. 辨析联合类型
interface Square {
kind: 'square';
size: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
type Shape = Square | Rectangle;
function area(s: Shape): number {
if (s.kind === 'square') {
return s.size * s.size;
} else if (s.kind === 'rectangle') {
return s.width * s.height
} else {
const _check: never = s;
return _check;
}
}
# 20. 索引签名
声明:
interface INumObj {
[index: number]: string;
}
let n: INumObj = { 1: 'a' };
interface IStrObj {
[key: string]: number;
}
let s: IStrObj = { 'a': 1 };
使用字符串字面量:
type props = 'a' | 'b' | 'c';
let obj: { [key in props]: number } = {
a: 1,
b: 2,
c: 3,
d: 4 // 错误
};
嵌套:
type NestedCss = {
color?: string;
children?: {
[key: string]: NestedCss;
}
}
let css: NestedCss = {
color: 'red',
children: {
'.head': {
color: 'green'
},
'.body': {
color: 'blue'
}
}
}
# 21. 类型移动
复制类型和值:
namespace Address {
export class Road {}
}
import Road = Address.Road;
let r: Road;
捕获类型:
// 捕获变量的类型
let num = 1;
let n: typeof num; // let num: number
// 捕获类成员的类型
class Person {
id = '1'
}
declare let p: Person;
let id: typeof p.id; // let id: string
// 捕获字面量类型
const a = 'a'; // const a: "a"
let b = 'b'; // let b: string
let c: typeof a; // let c: "a"
捕获对象的键名:
const person = {
name: '张三',
age: 18,
};
// type PersonType = { name: string; age: number; }
type PersonType = typeof person;
// type PersonKeysType = "name" | "age"
type PersonKeysType = keyof PersonType;
type KeysType = keyof typeof person; // "name" | "age"
# 22. 异常处理
抛出异常: throw new Error('error message')
捕获异常: try {} catch(e) {}
错误子类型:
RangeError
: 数字或参数 超出有效范围ReferenceError
: 使用未定义的变量SyntaxError
: 语法错误TypeError
: 类型错误,如 在字符串类型变量上调用数字类型的方法URIError
: decodeURI 、encodeURI 传入无效参数时
避免抛出非 Error 类型的东西:
throw '这是一个错误'; // 不推荐
throw new Error('这是一个错误'); // 使用 Error 能自动跟踪堆栈和错误位置
通过回调返回错误:
function doSome(content: string, callback: (e?: Error) => void) {
if (content == null) {
callback(new Error('content 不能为空'));
return;
}
callback()
}
不返回错误对象:
function validate(value: number): { error?: string } {
if (value < 100) {
return { error: 'value is less than 100' };
}
return {}
}
# 23. ThisType
指定函数、方法的 this
:
let person = {
name: '张三',
}
function sayHello(this: typeof person, age: number) {
console.log(`hello, I'm ${ this.name }, ${ age } years old.`)
}
sayHello.bind(person)(18)
ThisType<T>
:
type ObjectDescriptor<TData, TMethods> = {
data?: TData;
methods?: TMethods & ThisType<TData & TMethods>
};
function makeObject<TData, TMethods>(desc: ObjectDescriptor<TData, TMethods>): TData & TMethods {
let data: Object = desc.data || {};
let methods: Object = desc.methods || {};
return { ...data, ...methods } as (TData & TMethods);
}
let obj = makeObject({
data: {
x: 0,
y: 0,
},
methods: {
moveBy(dx: number, dy: number) {
this.x += dx;
this.y += dy;
}
}
});
obj.moveBy(10, 20);
console.log(obj);
/*
{
"x": 10,
"y": 20
}
*/
上一篇: 下一篇: