TypeScript 教程
TypeScript 是 JavaScript 的一个超集,由 Microsoft 开发并维护,它添加了可选的静态类型系统和基于类的面向对象编程等特性。本教程将详细介绍 TypeScript 的核心概念和用法。
目录
- TypeScript 简介
- 安装与配置
- 基础类型
- 变量声明
- 接口
- 类
- 函数
- 泛型
- 枚举
- 高级类型
- 模块
- 命名空间
- 装饰器
- 与 JavaScript 互操作
- 配置 tsconfig.json
- 最佳实践
TypeScript 简介
TypeScript 是 JavaScript 的超集,意味着所有有效的 JavaScript 代码都是有效的 TypeScript 代码。TypeScript 的主要特性包括:
- 静态类型检查
- 基于类的面向对象编程
- 接口和泛型
- 装饰器
- 模块和命名空间
- 与现有 JavaScript 代码兼容
- 编译时错误检查
安装与配置
安装 TypeScript
通过 npm 全局安装 TypeScript:
npm install -g typescript
验证安装:
tsc --version
编译 TypeScript 文件
创建一个 .ts
文件,例如 app.ts
,然后编译它:
tsc app.ts
这会生成一个 app.js
文件。
初始化 TypeScript 项目
tsc --init
这会创建一个 tsconfig.json
文件,包含所有编译选项。
基础类型
TypeScript 支持 JavaScript 的所有数据类型,并添加了额外的类型:
// 布尔值
let isDone: boolean = false;
// 数字
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
// 字符串
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.`;
// 数组
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3]; // 泛型语法
// 元组
let x: [string, number];
x = ["hello", 10]; // OK
x = [10, "hello"]; // Error
// 枚举
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
// Any
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;
// Void
function warnUser(): void {
console.log("This is my warning message");
}
// Null 和 Undefined
let u: undefined = undefined;
let n: null = null;
// Never
function error(message: string): never {
throw new Error(message);
}
// Object
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
// 类型断言
let someValue: any = "this is a string";
let strLength1: number = (<string>someValue).length;
let strLength2: number = (someValue as string).length;
变量声明
TypeScript 支持 let
和 const
:
let a = 10;
const b = 20;
// 解构
let [first, second] = [1, 2];
let { c, d } = { c: "foo", d: "bar" };
// 默认值
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
}
接口
接口是 TypeScript 的核心概念之一,用于定义对象的形状:
interface LabelledValue {
label: string;
size?: number; // 可选属性
readonly x: number; // 只读属性
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object", x: 5 };
printLabel(myObj);
// 函数类型接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string) {
return src.search(sub) > -1;
}
// 可索引类型接口
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
// 类类型接口
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
}
// 继承接口
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
类
TypeScript 提供了基于类的面向对象编程:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
// 继承
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);
}
}
// 公共、私有与受保护的修饰符
class Person {
public name: string; // 默认就是 public
private age: number; // 只能在类内部访问
protected gender: string; // 可以在派生类中访问
constructor(name: string, age: number, gender: string) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
// 存取器
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
// 静态属性
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number}) {
let xDist = point.x - Grid.origin.x;
let yDist = point.y - Grid.origin.y;
return Math.sqrt(xDist * xDist + yDist * yDist);
}
}
// 抽象类
abstract class Department {
constructor(public name: string) {}
abstract printMeeting(): void;
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing");
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
}
函数
TypeScript 为 JavaScript 函数添加了类型支持:
// 函数类型
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number { return x + y; };
// 可选参数和默认参数
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + " " + lastName;
} else {
return firstName;
}
}
function buildName2(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
// 剩余参数
function buildName3(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName3("Joseph", "Samuel", "Lucas", "MacKinzie");
// this 参数
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
// 重载
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
} else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
泛型
泛型用于创建可重用的组件:
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString");
let output2 = identity("myString"); // 类型推断
// 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
let myIdentity: GenericIdentityFn<number> = identity;
// 泛型类
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; };
// 泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// 在泛型约束中使用类型参数
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"); // okay
getProperty(x, "m"); // error
// 使用类类型
function create<T>(c: {new(): T; }): T {
return new c();
}
枚举
枚举类型是对 JavaScript 标准数据类型的一个补充:
// 数字枚举
enum Direction {
Up = 1,
Down,
Left,
Right
}
// 字符串枚举
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
// 异构枚举
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES"
}
// 计算的和常量成员
enum FileAccess {
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
G = "123".length
}
// 联合枚举与枚举成员的类型
enum ShapeKind {
Circle,
Square
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
// 运行时的枚举
enum E {
X, Y, Z
}
function f(obj: { X: number }) {
return obj.X;
}
f(E); // 可以工作,因为 E 有一个名为 X 的属性
// 反向映射
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
// const 枚举
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
高级类型
TypeScript 提供了多种高级类型:
// 交叉类型
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
// 联合类型
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 '${padding}'.`);
}
// 类型保护
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
// 用户自定义的类型保护
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
// typeof 类型保护
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 '${padding}'.`);
}
// instanceof 类型保护
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 类型细化为 'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 类型细化为 'StringPadder'
}
// 可为 null 的类型
let s = "foo";
s = null; // 错误, 'null'不能赋值给'string'
let sn: string | null = "bar";
sn = null; // 可以
// 类型别名
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === "string") {
return n;
} else {
return n();
}
}
// 字符串字面量类型
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 {
// error! should not pass null or undefined.
}
}
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error
// 数字字面量类型
function rollDie(): 1 | 2 | 3 | 4 | 5 | 6 {
// ...
}
// 可辨识联合
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
// 索引类型
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n => o[n]);
}
interface Person {
name: string;
age: number;
}
let person: Person = {
name: 'Jarid',
age: 35
};
let strings: string[] = pluck(person, ['name']); // ok, string[]
// 映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Partial<T> = {
[P in keyof T]?: T[P];
}
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;
模块
TypeScript 支持 ES6 模块语法:
// 导出
export interface StringValidator {
isAcceptable(s: string): boolean;
}
export const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
// 导入
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
// 重命名导入/导出
export { ZipCodeValidator as mainValidator };
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
// 默认导出
export default class ZipCodeValidator {
static numberRegexp = /^[0-9]+$/;
isAcceptable(s: string) {
return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
}
}
import validator from "./ZipCodeValidator";
let myValidator = new validator();
// 导出所有
export * from "./StringValidator";
export * from "./ZipCodeValidator";
// 动态模块加载
declare function require(moduleName: string): any;
import { ZipCodeValidator as Zip } from "./ZipCodeValidator";
if (needZipValidation) {
let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
let validator = new ZipCodeValidator();
if (validator.isAcceptable("...")) { /* ... */ }
}
命名空间
命名空间(以前称为内部模块)用于组织代码:
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
// 使用命名空间
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// 多文件命名空间
// Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
// LettersOnlyValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
// ZipCodeValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
// Test.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />
// 别名
namespace Shapes {
export namespace Polygons {
export class Triangle { }
export class Square { }
}
}
import polygons = Shapes.Polygons;
let sq = new polygons.Square();
装饰器
装饰器是一种特殊类型的声明,可以附加到类声明、方法、访问符、属性或参数上:
// 类装饰器
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;
}
}
// 方法装饰器
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;
}
}
// 访问器装饰器
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() { return this._x; }
@configurable(false)
get y() { return this._y; }
}
// 属性装饰器
import "reflect-metadata";
const formatMetadataKey = Symbol("format");
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}
// 参数装饰器
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
let method = descriptor.value;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
}
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@validate
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}
与 JavaScript 互操作
TypeScript 可以与现有的 JavaScript 代码一起工作:
// 类型声明
declare var jQuery: (selector: string) => any;
// 使用
jQuery('#foo');
// 声明文件
// 通常放在 .d.ts 文件中
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export let sep: string;
}
// 使用
/// <reference path="node.d.ts" />
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");
配置 tsconfig.json
tsconfig.json
文件指定了编译项目所需的根文件和编译选项:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
常用编译选项:
target
: 编译目标版本 (es3, es5, es6/es2015, es2016, es2017, es2018, es2019, es2020, esnext)module
: 模块系统 (commonjs, amd, system, umd, es2015, esnext)strict
: 启用所有严格类型检查选项noImplicitAny
: 禁止隐式 any 类型strictNullChecks
: 启用严格的 null 检查outDir
: 输出目录rootDir
: 输入文件目录sourceMap
: 生成 source map 文件declaration
: 生成 .d.ts 声明文件
最佳实践
- 启用严格模式:在
tsconfig.json
中设置"strict": true
- 使用明确的类型:避免使用
any
类型 - 利用接口:定义对象形状和函数签名
- 使用泛型:创建可重用组件
- 模块化代码:使用 ES6 模块语法
- 编写声明文件:为现有 JavaScript 代码提供类型信息
- 使用类型推断:让 TypeScript 自动推断类型
- 利用高级类型:如联合类型、交叉类型、映射类型等
- 保持代码一致:使用一致的命名和代码风格
- 逐步迁移:可以逐步将 JavaScript 项目迁移到 TypeScript
总结
TypeScript 通过添加静态类型系统和其他高级特性,极大地增强了 JavaScript 的开发体验。它提供了更好的工具支持、更早的错误检测和更清晰的代码结构,同时保持与现有 JavaScript 生态系统的兼容性。本教程涵盖了 TypeScript 的核心概念,从基础类型到高级特性,帮助你开始使用 TypeScript 进行开发。