TypeScript 技术文档
目录
1. 简介
TypeScript 是一种由微软开发的开源编程语言,它在 JavaScript 的基础上增加了静态类型定义。TypeScript 可以编译成纯 JavaScript,因此可以在任何支持 JavaScript 的环境中运行。它的设计目标是让大型 JavaScript 项目更容易开发和维护。
主要特点:
- 类型系统:TypeScript 提供静态类型检查,能够在编译时发现潜在的错误。
- 现代 JavaScript 特性:支持 ES6/ES7 的所有新特性,比如箭头函数、解构赋值、模块化等。
- 兼容性:TypeScript 是 JavaScript 的超集,所有合法的 JavaScript 代码在 TypeScript 中也是合法的。
- 强大的工具支持:TypeScript 有丰富的 IDE 支持,如 Visual Studio Code,可以提供代码补全、重构、导航等功能。
2. 安装与配置
要开始使用 TypeScript,你需要安装 Node.js 和 npm。安装完 Node.js 后,可以使用 npm 来安装 TypeScript:
npm install -g typescript
安装完成后,你可以使用 tsc
命令来编译 TypeScript 文件:
tsc --version
创建一个 TypeScript 文件,例如 hello.ts
:
let message: string = 'Hello, TypeScript!';
console.log(message);
编译并运行:
tsc hello.ts
node hello.js
为了更方便地管理 TypeScript 项目,可以使用 tsconfig.json
文件来配置编译选项。创建一个默认的 tsconfig.json
文件:
tsc --init
tsconfig.json
示例:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
3. 基本类型
TypeScript 提供了一系列基本类型,涵盖了 JavaScript 中的所有基本数据类型。
3.1 布尔值
let isDone: boolean = false;
3.2 数字
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
3.3 字符串
let color: string = 'blue';
color = 'red';
可以使用模板字符串:
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`;
3.4 数组
let list: number[] = [1, 2, 3];
或使用泛型数组类型:
let list: Array<number> = [1, 2, 3];
3.5 元组
let x: [string, number];
x = ['hello', 10];
3.6 枚举
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
3.7 Any
当你不确定变量类型时,可以使用 any
类型:
let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;
3.8 Void
用于表示没有任何类型,常用于没有返回值的函数:
function warnUser(): void {
console.log('This is my warning message');
}
3.9 Null 和 Undefined
let u: undefined = undefined;
let n: null = null;
3.10 Never
表示那些永不存在的值的类型。例如,永远不会返回的函数表达式或箭头函数的返回值类型:
function error(message: string): never {
throw new Error(message);
}
3.11 Object
表示非原始类型:
declare function create(o: object | null): void;
create({ prop: 0 });
create(null);
4. 接口
接口是 TypeScript 中的核心原则之一,用于定义对象的类型。接口能够描述一个对象的形状,能够检查对象是否符合特定的结构。
4.1 简单示例
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);
在这个例子中,LabelledValue
接口定义了一个 label
属性,并且 printLabel
函数期望一个实现了 LabelledValue
接口的对象作为参数。即使 myObj
有其他属性,但只要它至少有一个 label
属性,TypeScript 就会认为它是合法的。
4.2 可选属性
可选属性接口允许一些属性存在,也允许一些属性不存在:
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
let newSquare = { color: 'white', area: 100 };
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({ color: 'black' });
在这个例子中,SquareConfig
接口定义了 color
和 width
可选属性。createSquare
函数根据传入的 config
对象动态地创建一个新的对象。
4.3 只读属性
一些属性可以在对象刚刚创建的时候修改其值,而在此之后将是只读的:
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // 错误:不能改变只读属性
在这个例子中,x
和 y
属性是只读的,因此它们的值一旦被赋值就不能再被修改。
4.4 函数类型
接口也可以描述函数类型:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
let result = source.search(subString);
return result > -1;
};
在这个例子中,SearchFunc
接口定义了一个函数类型,该函数接收两个字符串参数并返回一个布尔值。
4.5 可索引类型
接口可以描述那些能够通过索引得到某种类型的对象,这在对象上可以用数字索引或字符串索引:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ['Bob', 'Fred'];
let myStr: string = myArray[0];
在这个例子中,StringArray
接口描述了具有数字索引的数组类型,并且返回值是字符串类型。
5. 类
TypeScript 支持所有 JavaScript 的类特性,并且增加了一些新的特性。
5.1 简单类
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return 'Hello, ' + this.greeting;
}
}
let greeter = new Greeter('world');
在这个例子中,Greeter
类有一个 greeting
属性和一个 greet
方法。greet
方法返回一个问候字符串。
5.2 继承
类可以扩展其他类,这意味着一个类可以继承另一个类的属性和方法:
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) {
console.log('Slithering...');
super.move(distanceInMeters);
}
}
let sam = new Snake('Sammy the Python');
sam.move();
在这个例子中,Snake
类扩展了 Animal
类,并且重写了 move
方法。
5.3 公共,私有与受保护修饰符
TypeScript 中的成员默认是公共的。你也可以用 private
修饰符将成员标记为私有的:
typescript
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
new Animal('Cat').name; // 错误:name 是私有的
可以使用 protected
修饰符声明受保护的成员,这些成员只能在类本身及其子类中访问:
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee('Howard', 'Sales');
console.log(howard.getElevatorPitch()); // 有效
console.log(howard.name); // 错误
5.4 readonly 修饰符
你可以使用 readonly
关键字将属性设置为只读:
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor(theName: string) {
this.name = theName;
}
}
let dad = new Octopus('Man with the 8 strong legs');
dad.name = 'Man with the 3-piece suit'; // 错误!name 是只读的
5.5 存取器
TypeScript 支持通过 get
和 set
关键字来定义存取器:
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
let employee = new Employee();
employee.fullName = 'Bob Smith';
console.log(employee.fullName);
在这个例子中,Employee
类有一个私有属性 _fullName
,并且通过存取器来设置和获取它的值。
6. 函数
TypeScript 中的函数与 JavaScript 中的函数类似,但在参数和返回类型上提供了更多的类型检查。
6.1 函数类型
你可以为函数的参数和返回值指定类型:
function add(x: number, y: number): number {
return x + y;
}
let myAdd: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
6.2 可选参数和默认参数
可以通过在参数名旁使用 ?
来实现可选参数:
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let result1 = buildName('Bob'); // 有效
let result2 = buildName('Bob', 'Adams'); // 有效
还可以为参数提供默认值:
function buildName(firstName: string, lastName = 'Smith'): string {
return firstName + ' ' + lastName;
}
let result1 = buildName('Bob'); // 有效,返回 "Bob Smith"
let result2 = buildName('Bob', 'Adams'); // 有效,返回 "Bob Adams"
6.3 剩余参数
可以使用 ...
语法将所有参数收集到一个变量中:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ');
}
let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie');
6.4 this 和箭头函数
在 TypeScript 中,this
的值取决于函数调用的位置。可以使用箭头函数来正确地捕获 this
值:
let deck = {
suits: ['hearts', 'spades', 'clubs', 'diamonds'],
cards: Array(52),
createCardPicker: function () {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit);
在这个例子中,箭头函数不会创建自己的 this
,它会捕获 deck
对象的 this
值。
7. 泛型
泛型是能够创建可重用组件的一种工具,能够使组件可以支持多种类型的数据。
7.1 泛型函数
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>('myString'); // 手动指定类型
let output2 = identity('myString'); // 类型推断
7.2 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
在这个例子中,GenericIdentityFn
接口描述了一个泛型函数类型,并且 myIdentity
是一个特定类型的泛型函数。
7.3 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
在这个例子中,GenericNumber
是一个泛型类,它可以处理任意类型的数字。
7.4 泛型约束
有时候我们想要限制某种类型的泛型函数,这时候可以使用泛型约束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity({ length: 10, value: 3 });
在这个例子中,loggingIdentity
函数要求传入的参数必须有 length
属性。
7.5 在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, 'a'); // 有效
getProperty(x, 'm'); // 错误:类型“"m"”的参数不能赋给类型“"a" | "b" | "c" | "d"”的参数
在这个例子中,getProperty
函数接受一个对象和一个属性名称,并返回该属性的值。K
被约束为 T
的属性名称。
8. 模块
模块在 TypeScript 中是用于组织代码的主要方式,它们有助于分离代码和避免全局作用域的污染。
8.1 导出
export interface StringValidator {
isAcceptable(s: string): boolean;
}
export const numberRegexp = /^[0-9]+$/;
8.2 导入
import { StringValidator } from './StringValidator';
let myValidator: StringValidator;
你可以使用 export default
导出一个默认的对象:
export default class ZipCodeValidator {
static numberRegexp = /^[0-9]+$/;
isAcceptable(s: string): boolean {
return ZipCodeValidator.numberRegexp.test(s);
}
}
然后可以使用 import
导入默认对象:
import ZipCodeValidator from './ZipCodeValidator';
let myValidator = new ZipCodeValidator();
9. 类型推断
TypeScript 能够根据代码中的一些简单的规则推断变量的类型。如果变量声明时没有指定类型,TypeScript 会自动推断出一个类型。
9.1 基础示例
let x = 3; // x 被推断为 number 类型
9.2 最佳通用类型
当需要从多个表达式中推断类型时,TypeScript 会选择最合适的通用类型:
let x = [0, 1, null]; // x 的类型推断为 (number | null)[]
10. 类型兼容性
TypeScript 中的类型兼容性是基于结构子类型的。结构类型系统是基于类型的成员来确定类型的兼容性。
10.1 接口兼容性
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
p = new Person(); // OK, 因为 Person 有一个兼容的 name 属性
10.2 函数兼容性
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // 错误
11. 高级类型
TypeScript 提供了许多高级类型操作,帮助你在编写复杂类型定义时提供更强的灵活性。
11.1 交叉类型
交叉类型 &
是将多个类型合并为一个类型:
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(result as any)[id] = (first as any)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(result as any)[id] = (second as any)[id];
}
}
return result;
}
let x = extend({ a: 'hello' }, { b: 42 });
let a = x.a; // string
let b = x.b; // number
11.2 联合类型
联合类型 |
表示一个值可以是几种类型之一:
function padLeft(value: string, padding: string | number) {
if (typeof padding === 'number') {
return Array(padding + 1).join(' ') + value;
}
if (typeof padding === 'string') {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
padLeft('Hello world', 4); // 返回 " Hello world"
padLeft('Hello world', '>>>'); // 返回 ">>>Hello world"
11.3 类型别名
类型别名可以为类型起一个新名字:
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
11.4 字面量类型
字面量类型约束一个变量的值只能是某个特定的值:
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out';
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === 'ease-in') {
// ...
} else if (easing === 'ease-out') {
// ...
} else if (easing === 'ease-in-out') {
// ...
} else {
// 错误:参数必须是 'ease-in','ease-out' 或 'ease-in-out'
}
}
}
let button = new UIElement();
button.animate(0, 0, 'ease-in'); // 有效
button.animate(0, 0, 'uneasy'); // 错误:参数不是有效的字面量
12. 装饰器
装饰器是一个特殊类型的声明,能够被附加到类声明、方法、访问器、属性或参数上。装饰器使用 @expression
这种形式,expression
必须求值为一个函数,它将在运行时被调用,被装饰的声明信息作为参数传入。
12.1 类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return 'Hello, ' + this.greeting;
}
}
12.2 方法装饰器
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return 'Hello, ' + this.greeting;
}
}
13. 编译选项
TypeScript 编译器可以通过命令行参数和 tsconfig.json
文件进行配置。
13.1 tsconfig.json
tsconfig.json
文件用于配置 TypeScript 项目。一个简单的示例:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
14. 常见问题与最佳实践
14.1 如何进行类型定义?
在 TypeScript 中进行类型定义时,推荐尽量使用接口,因为接口可以被类实现和扩展,并且更加灵活和易于阅读。
14.2 何时使用类型断言?
类型断言用于告诉编译器某个值的具体类型:
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
尽量避免过度使用类型断言,因为它可能隐藏潜在的类型错误。
14.3 如何处理第三方库的类型定义?
可以使用 DefinitelyTyped 项目提供的类型定义文件:
npm install @types/jquery --save-dev
这样就可以在 TypeScript 项目中使用 jQuery 的类型定义文件。
14.4 如何调试 TypeScript 代码?
可以通过生成 Source Map 文件来调试 TypeScript 代码:
{
"compilerOptions": {
"sourceMap": true
}
}
这样就可以在调试工具中直接看到 TypeScript 代码,并且设置断点进行调试。
14.5 如何提高代码质量?
- 启用严格模式:在
tsconfig.json
中启用strict
选项。 - 使用代码格式化工具:如 Prettier 来保持代码风格一致。
- 使用代码静态分析工具:如 ESLint 来发现和修复代码中的潜在问题。
- 编写单元测试:确保代码的正确性和稳定性。
总结
TypeScript 通过增加静态类型检查和现代 JavaScript 特性,为开发大型 JavaScript 项目提供了更强的开发体验和更高的代码质量。通过合理使用 TypeScript 的类型系统、接口、类、泛型、模块、装饰器等特性,开发者可以更高效地构建可维护、可扩展的 JavaScript 应用程序。