基础
TypeScript 环境编译
1. 使用 ts-node
- 适用于开发环境快速运行 TypeScript 代码
- 安装:
npm install -g ts-node typescript
- 使用:
ts-node script.ts
- 优点:无需编译即可直接运行,开发效率高
- 缺点:不适合生产环境,运行性能较差
2. 使用 tsc 编译 (推荐)
- TypeScript 官方编译器
- 安装:
npm install -g typescript
- 编译:
tsc script.ts
- 配置文件:
tsconfig.json
json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "./dist",
"strict": true
}
}
- 优点:
- 可靠的编译结果
- 完整的类型检查
- 适合生产环境
- 缺点:需要额外的编译步骤
3. 使用打包工具
Webpack
- 安装:
npm install webpack typescript ts-loader --save-dev
- webpack.config.js 配置:
js
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
};
Rollup
- 安装:
npm install rollup @rollup/plugin-typescript --save-dev
- rollup.config.js 配置:
js
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/index.ts',
output: {
file: 'bundle.js',
format: 'cjs',
},
plugins: [typescript()],
};
TypeScript 的基础
TypeScript 类型
1. 原始数据类型
number:数字类型
typescriptlet decimal: number = 6; let hex: number = 0xf00d; let binary: number = 0b1010;
string:字符串类型
typescriptlet name: string = '张三'; let greeting: string = `你好,${name}`;
boolean:布尔类型
typescriptlet isDone: boolean = false;
undefined:未定义
typescriptlet u: undefined = undefined;
null:空值
typescriptlet n: null = null;
2. 特殊类型
void:表示没有任何类型,通常用于函数返回值
typescriptfunction warnUser(): void { console.log('警告信息'); }
any:任意类型,相当于放弃类型检查
typescriptlet notSure: any = 4; notSure = '可以是字符串'; notSure = false; // 也可以是布尔值
unknown:比 any 更安全的类型,需要类型检查或类型断言才能使用
typescriptlet value: unknown = 4; // 需要类型检查或类型断言才能使用 if (typeof value === 'string') { console.log(value.toUpperCase()); }
never:永不存在的值的类型
typescript// 抛出异常的函数 function error(message: string): never { throw new Error(message); } // 永远不会返回的函数 function infiniteLoop(): never { while (true) {} }
3. 复合类型
数组
typescriptlet list: number[] = [1, 2, 3]; let list2: Array<number> = [1, 2, 3];
元组
typescriptlet tuple: [string, number] = ['hello', 10];
对象
typescriptlet obj: object = { name: '张三', age: 20, };
4. 类型推论
typescript
// TypeScript 会自动推断类型
let x = 3; // 推断为 number
let arr = [1, 2, 3]; // 推断为 number[]
5. 类型断言
typescript
let someValue: unknown = 'this is a string';
// 两种断言方式
let strLength1: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;
// 当你确定类型兼容但TypeScript无法识别时使用类型断言
const element = document.getElementById('root') as HTMLElement;
// 双重断言用于不直接兼容的类型转换(谨慎使用)
const str = 'hello' as any as number; // 不推荐
6. 函数和类
typescript
// 基本函数类型
function add(x: number, y: number): number {
return x + y;
}
// 可选参数
function greeting(name?: string): string {
return name ? `你好, ${name}` : '你好!';
}
// 默认参数
function welcome(name: string = '访客'): string {
return `欢迎, ${name}!`;
}
// 函数重载
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
return typeof x === 'number'
? Number(x.toString().split('').reverse().join(''))
: x.split('').reverse().join('');
}
// 基本类
class Person {
// 成员属性
private name: string;
protected age: number;
public readonly id: number;
// 构造函数
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.id = Math.random();
}
// 实例方法
public sayHello(): void {
console.log(`大家好,我是${this.name},今年${this.age}岁`);
}
// 静态方法
static createPerson(name: string, age: number): Person {
return new Person(name, age);
}
// getter/setter
get personName(): string {
return this.name;
}
set personName(value: string) {
this.name = value;
}
}
// 类的继承
class Student extends Person {
private grade: string;
constructor(name: string, age: number, grade: string) {
super(name, age);
this.grade = grade;
}
public study(): void {
console.log(`${this.personName}正在学习`);
}
}
// 接口实现
interface Flyable {
fly(): void;
}
class Bird implements Flyable {
fly(): void {
console.log('鸟儿在飞翔');
}
}
总结
- 尽量避免使用
any
类型,它会失去 TypeScript 的类型保护 - 优先使用
unknown
替代any
,因为unknown
更安全 - 合理使用类型推论,避免过度类型标注
- 必要时使用类型断言,但要谨慎使用
ts
const a = 12;
联合和交叉类型
1. 联合类型(Union Types)
- 使用
|
符号定义可以是多种类型之一的值
typescript
// 基础联合类型
let mixedType: string | number;
mixedType = 'hello'; // OK
mixedType = 123; // OK
mixedType = true; // Error
// 函数参数的联合类型
function printId(id: number | string) {
if (typeof id === 'string') {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
// 数组的联合类型
let arr: (number | string)[] = [1, 'a', 2, 'b'];
2. 交叉类型(Intersection Types)
- 使用
&
符号将多个类型合并为一个类型
typescript
// 基础交叉类型
interface BusinessPartner {
name: string;
credit: number;
}
interface Identity {
id: number;
email: string;
}
type Employee = BusinessPartner & Identity;
// 使用交叉类型
let emp: Employee = {
name: '张三',
credit: 100,
id: 1234,
email: 'zhangsan@example.com',
};
3. 类型保护(Type Guards)
typescript
// 使用 typeof 进行类型保护
function padLeft(value: string, padding: string | number) {
if (typeof padding === 'number') {
return ' '.repeat(padding) + value;
}
return padding + value;
}
// 使用 instanceof 进行类型保护
class Bird {
fly() {
console.log('鸟儿飞');
}
}
class Fish {
swim() {
console.log('鱼儿游');
}
}
function move(pet: Bird | Fish) {
if (pet instanceof Bird) {
pet.fly();
} else {
pet.swim();
}
}
// 使用 in 操作符进行类型保护
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInfo(emp: UnknownEmployee) {
console.log(`名字: ${emp.name}`);
if ('privileges' in emp) {
// 现在 TypeScript 知道这是 Admin 类型
console.log(`特权: ${emp.privileges.join(', ')}`);
}
if ('startDate' in emp) {
// 现在 TypeScript 知道这是 Employee 类型
console.log(`入职时间: ${emp.startDate}`);
}
}
4. 可辨识联合(Discriminated Unions)
typescript
interface Circle {
kind: 'circle';
radius: number;
}
interface Square {
kind: 'square';
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
}
}
总结
联合类型:
- 当值可能是多种类型之一时使用
- 配合类型保护确保类型安全
- 避免过多类型的联合,会增加代码复杂度
交叉类型:
- 用于组合多个接口或类型
- 确保合并的类型之间没有冲突
- 优先使用接口继承而不是复杂的交叉类型
包装类型(Wrapper Types)
在 TypeScript 中,包装类型是对原始类型的对象包装。主要包括 String、Number、Boolean、Symbol、BigInt。
包装类型与原始类型的区别
- 类型不同
typescript
// 原始类型
let str: string = 'hello';
let num: number = 123;
let bool: boolean = true;
// 包装类型
let strObj: String = new String('hello');
let numObj: Number = new Number(123);
let boolObj: Boolean = new Boolean(true);
// 类型检查
typeof str; // 'string'
typeof strObj; // 'object'
- 内存占用
typescript
// 原始类型直接存储值
let x: number = 123;
// 包装类型需要创建对象,占用更多内存
let y: Number = new Number(123);
- 方法调用
typescript
// 原始类型会被临时转换为包装类型来调用方法
let str: string = 'hello';
console.log(str.toUpperCase()); // 'HELLO'
// 包装类型可以直接调用方法
let strObj: String = new String('hello');
console.log(strObj.toUpperCase()); // 'HELLO'
- 相等性比较
typescript
let str: string = 'hello';
let strObj: String = new String('hello');
console.log(str === 'hello'); // true
console.log(strObj === 'hello'); // false
console.log(strObj.valueOf() === 'hello'); // true
总结
- 除非特殊需求,始终使用原始类型(小写)而不是包装类型(大写)
- 避免使用 new String()、new Number()、new Boolean() 创建包装对象
- 如果需要类型转换,使用 String()、Number()、Boolean() 函数而不是构造函数
- 记住包装类型主要是为了提供额外的方法和属性,而不是为了替代原始类型
TypeScript 类型层级
TypeScript 的类型系统呈现出一个层级结构,从顶层到底层可以表示为:
any / unknown (顶层类型)
↓
Object / object
↓
包装类型(String/Number/Boolean/Symbol/BigInt)
↓
原始类型(string/number/boolean/symbol/bigint)
↓
字面量类型("hello"/42/true)
↓
never (底层类型)
类型兼容性规则
1. 基础类型兼容性
typescript
// 1.1 基础类型的层级关系
let str: string = 'hello';
let num: number = 42;
let bool: boolean = true;
let anyVal: any = str; // ✅ 任何类型都可以赋值给 any
let unknownVal: unknown = str; // ✅ 任何类型都可以赋值给 unknown
// 1.2 字面量类型到基础类型
let literalStr: 'hello' = 'hello';
let literalNum: 42 = 42;
let strVal: string = literalStr; // ✅ 字面量类型可以赋值给对应的基础类型
let numVal: number = literalNum; // ✅ 字面量类型可以赋值给对应的基础类型
// 1.3 联合类型的兼容性
type StringOrNumber = string | number;
let union: StringOrNumber = 'hello'; // ✅
union = 42; // ✅ 联合类型可以接受其中任一类型的值
2. 对象类型兼容性
typescript
// 2.1 基本对象兼容性
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
let point2D: Point2D;
let point3D: Point3D = { x: 1, y: 2, z: 3 };
point2D = point3D; // ✅ 多的属性可以赋值给少的
// 2.2 可选属性
interface Options {
title?: string;
width?: number;
}
let opt1: Options = {}; // ✅ 可选属性都可以不传
let opt2: Options = { title: '标题' }; // ✅ 可以只传部分可选属性
// 2.3 只读属性
interface ReadonlyPoint {
readonly x: number;
readonly y: number;
}
interface MutablePoint {
x: number;
y: number;
}
let readonlyPoint: ReadonlyPoint;
let mutablePoint: MutablePoint;
readonlyPoint = mutablePoint; // ✅ 可变类型可以赋值给只读类型
// mutablePoint = readonlyPoint; // ❌ 只读类型不能赋值给可变类型
3. 函数类型兼容性
typescript
// 3.1 参数数量
type Func1 = (a: number) => void;
type Func2 = (a: number, b?: string) => void;
let f1: Func1;
let f2: Func2;
f1 = f2; // ✅ 参数多的函数可以赋值给参数少的函数
// f2 = f1; // ❌ 参数少的函数不能赋值给参数多的函数
// 3.2 参数类型
type Handler1 = (event: Event) => void;
type Handler2 = (event: MouseEvent) => void;
let h1: Handler1;
let h2: Handler2;
h1 = h2; // ✅ 更具体的类型可以赋值给更宽泛的类型
// h2 = h1; // ❌ 更宽泛的类型不能赋值给更具体的类型
// 3.3 返回值类型
type Getter1 = () => { x: number };
type Getter2 = () => { x: number; y: number };
let g1: Getter1;
let g2: Getter2;
g1 = g2; // ✅ 返回值类型遵循对象类型的兼容性规则
4. 泛型类型兼容性
typescript
// 4.1 基本泛型兼容性
interface Box<T> {
value: T;
}
let numberBox: Box<number> = { value: 42 };
let stringBox: Box<string> = { value: 'hello' };
// numberBox = stringBox; // ❌ 不同的泛型参数导致类型不兼容
// 4.2 泛型函数兼容性
interface Event<T> {
data: T;
}
type Handler<T> = (event: Event<T>) => void;
let numberHandler: Handler<number>;
let anyHandler: Handler<any>;
anyHandler = numberHandler; // ✅ 具体类型的处理器可以赋值给 any 类型的处理器
5. 类的兼容性
typescript
// 5.1 基本类兼容性
class Animal {
name: string = '';
}
class Dog extends Animal {
breed: string = '';
}
let animal: Animal;
let dog: Dog = new Dog();
animal = dog; // ✅ 子类实例可以赋值给父类类型
// 5.2 私有成员和保护成员
class Parent {
private secret: string = '';
protected shared: string = '';
}
class Child extends Parent {
useShared() {
this.shared; // ✅ 可以访问protected成员
// this.secret; // ❌ 不能访问private成员
}
}
// 5.3 静态成员和实例成员
class StaticClass {
static count: number = 0;
instanceValue: string = '';
}
type StaticType = typeof StaticClass; // 获取类的静态部分类型
type InstanceType = StaticClass; // 获取类的实例类型
总结
- 类型兼容性基于结构类型系统,关注的是类型的结构而不是名称
- 对象类型的兼容性取决于它们的属性
- 函数类型的兼容性取决于参数和返回值
- 使用类型断言时要谨慎,优先使用类型收窄
- 理解泛型的类型兼容性对于编写灵活的代码很重要