TypeScript 完全入门指南:从JavaScript到强类型编程
TypeScript
JavaScript
类型系统
强类型编程
前端开发
静态检查
TypeScript是JavaScript的超集,为动态语言添加了静态类型检查。本文将从基础语法到高级特性,全面介绍TypeScript的使用方法,帮助您构建更安全、更易维护的应用程序。
为什么选择 TypeScript?
TypeScript解决了JavaScript开发中的许多痛点:
- 类型安全:在编译时捕获类型错误
- 更好的IDE支持:智能提示、重构、导航
- 代码文档化:类型即文档
- 渐进式采用:可以逐步从JavaScript迁移
- 现代JavaScript特性:支持最新的ECMAScript标准
环境搭建
# 安装TypeScript编译器
npm install -g typescript
# 创建TypeScript配置文件
tsc --init
# 编译TypeScript文件
tsc app.ts
# 监听模式
tsc app.ts --watch
# 使用ts-node直接运行TypeScript
npm install -g ts-node
ts-node app.ts
基础类型系统
// 基本类型
let isDone: boolean = false;
let count: number = 42;
let name: string = 'TypeScript';
// 数组类型
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ['Alice', 'Bob', 'Charlie'];
// 元组类型
let tuple: [string, number] = ['hello', 10];
// 枚举类型
enum Color {
Red,
Green,
Blue
}
let favoriteColor: Color = Color.Blue;
// 字符串枚举
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
// any类型(尽量避免使用)
let notSure: any = 4;
notSure = 'maybe a string';
notSure = false;
// void类型
function warnUser(): void {
console.log('This is a warning message');
}
// null和undefined
let u: undefined = undefined;
let n: null = null;
// never类型(永不返回的函数)
function error(message: string): never {
throw new Error(message);
}
接口和对象类型
// 基本接口
interface User {
id: number;
name: string;
email: string;
age?: number; // 可选属性
readonly created: Date; // 只读属性
}
// 使用接口
const user: User = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
created: new Date()
};
// 函数类型接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: SearchFunc = function(source: string, subString: string): boolean {
return source.search(subString) > -1;
};
// 可索引类型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ['Bob', 'Fred'];
// 类接口
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {}
}
// 继承接口
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
const square: Square = {
color: 'blue',
sideLength: 10
};
函数类型
// 函数类型定义
function add(x: number, y: number): number {
return x + y;
}
// 可选参数
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + ' ' + lastName;
}
return firstName;
}
// 默认参数
function buildNameWithDefault(firstName: string, lastName = 'Doe'): string {
return firstName + ' ' + lastName;
}
// 剩余参数
function buildNameList(firstName: string, ...restOfName: string[]): string {
return firstName + ' ' + restOfName.join(' ');
}
// 函数重载
function pickCard(x: {suit: string; card: number}[]): number;
function pickCard(x: number): {suit: string; card: number};
function pickCard(x: any): 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: ['spades', 'hearts', 'clubs', 'diamonds'][pickedSuit], card: x % 13 };
}
}
// 箭头函数
const multiply = (x: number, y: number): number => x * y;
// 回调函数类型
function processData(data: string[], callback: (item: string) => void): void {
data.forEach(callback);
}
processData(['a', 'b', 'c'], (item) => {
console.log(item.toUpperCase());
});
类和继承
// 基本类定义
class Animal {
private name: string;
protected species: string;
public age: number;
constructor(name: string, species: string, age: number) {
this.name = name;
this.species = species;
this.age = age;
}
// 公共方法
public makeSound(): string {
return `${this.name} makes a sound`;
}
// 受保护方法
protected getInfo(): string {
return `${this.name} is a ${this.species}`;
}
// 静态方法
static createAnimal(name: string, species: string, age: number): Animal {
return new Animal(name, species, age);
}
// getter和setter
get displayName(): string {
return this.name;
}
set displayName(newName: string) {
this.name = newName;
}
}
// 类继承
class Dog extends Animal {
private breed: string;
constructor(name: string, age: number, breed: string) {
super(name, 'Dog', age);
this.breed = breed;
}
// 重写父类方法
public makeSound(): string {
return `${this.displayName} barks: Woof!`;
}
// 子类特有方法
public wagTail(): string {
return `${this.displayName} is wagging tail`;
}
// 访问受保护成员
public getAnimalInfo(): string {
return this.getInfo() + ` and is a ${this.breed}`;
}
}
// 抽象类
abstract class Shape {
abstract area(): number;
displayArea(): void {
console.log(`Area: ${this.area()}`);
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
area(): number {
return Math.PI * this.radius * this.radius;
}
}
// 使用类
const myDog = new Dog('Buddy', 3, 'Golden Retriever');
console.log(myDog.makeSound());
console.log(myDog.wagTail());
const circle = new Circle(5);
circle.displayArea();
泛型
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>('hello');
let output2 = identity<number>(42);
let output3 = identity('auto inferred'); // 类型推断
// 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
function myIdentity<T>(arg: T): T {
return arg;
}
let myGenericIdentity: GenericIdentityFn<number> = myIdentity;
// 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
constructor(zeroValue: T, addFn: (x: T, y: T) => T) {
this.zeroValue = zeroValue;
this.add = addFn;
}
}
let myGenericNumber = new GenericNumber<number>(0, (x, y) => 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): T[K] {
return obj[key];
}
let person = { name: 'John', age: 30, city: 'New York' };
let personName = getProperty(person, 'name'); // string类型
let personAge = getProperty(person, 'age'); // number类型
// 条件类型
type NonNullable<T> = T extends null | undefined ? never : T;
type Example1 = NonNullable<string | null>; // string
type Example2 = NonNullable<string | undefined>; // string
高级类型
// 联合类型
type StringOrNumber = string | number;
function padLeft(value: string, padding: StringOrNumber): string {
if (typeof padding === 'number') {
return Array(padding + 1).join(' ') + value;
}
if (typeof padding === 'string') {
return padding + value;
}
throw new Error('Expected string or number');
}
// 交叉类型
interface ErrorHandling {
success: boolean;
error?: { message: string };
}
interface ArtworksData {
artworks: { title: string }[];
}
type ArtworksResponse = ArtworksData & ErrorHandling;
// 类型别名
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 EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// ...
}
// 数字字面量类型
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
return (Math.floor(Math.random() * 6) + 1) as 1 | 2 | 3 | 4 | 5 | 6;
}
// 索引类型
function pluck<T, K extends keyof T>(obj: T, names: K[]): T[K][] {
return names.map(n => obj[n]);
}
interface Person {
name: string;
age: number;
}
let person1: Person = {
name: 'Jarid',
age: 35
};
let strings: string[] = pluck(person1, ['name']); // 正确
// let invalidStrings: string[] = pluck(person1, ['name', 'unknown']); // 错误
// 映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PersonPartial = Partial<Person>;
type PersonReadonly = Readonly<Person>;
模块和命名空间
// math.ts - 导出模块
export function add(x: number, y: number): number {
return x + y;
}
export function subtract(x: number, y: number): number {
return x - y;
}
export const PI = 3.14159;
// 默认导出
export default class Calculator {
add(x: number, y: number): number {
return x + y;
}
}
// main.ts - 导入模块
import Calculator, { add, subtract, PI } from './math';
import * as MathUtils from './math';
const calc = new Calculator();
const result1 = add(1, 2);
const result2 = MathUtils.subtract(5, 3);
// 命名空间
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();
// 声明合并
interface User {
name: string;
}
interface User {
age: number;
}
// 合并后的User接口包含name和age
装饰器
// 需要在tsconfig.json中启用:"experimentalDecorators": true
// 类装饰器
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 Person {
constructor(private name: string) {}
@enumerable(false)
greet() {
return `Hello, ${this.name}`;
}
}
// 属性装饰器
function format(formatString: string) {
return function (target: any, propertyKey: string) {
let value = target[propertyKey];
const getter = function () {
return `${formatString} ${value}`;
};
const setter = function (newVal: string) {
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class Greeter2 {
@format('Hello')
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
// 参数装饰器
function logParameter(target: any, propertyName: string, parameterIndex: number) {
console.log(`Parameter ${parameterIndex} in ${propertyName} has been decorated`);
}
class UserService {
getUser(@logParameter id: number): User {
// 获取用户逻辑
return { name: 'John', age: 30 };
}
}
实用工具类型
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial - 所有属性变为可选
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number; }
// Required - 所有属性变为必需
type RequiredUser = Required<PartialUser>;
// Readonly - 所有属性变为只读
type ReadonlyUser = Readonly<User>;
// Pick - 选择特定属性
type UserSummary = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
// Omit - 排除特定属性
type UserWithoutAge = Omit<User, 'age'>;
// { id: number; name: string; email: string; }
// Record - 创建具有特定键值类型的对象类型
type UserRoles = Record<string, User>;
// { [key: string]: User; }
// Exclude - 从联合类型中排除某些类型
type T1 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
// Extract - 从联合类型中提取某些类型
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'
// NonNullable - 排除null和undefined
type T3 = NonNullable<string | number | undefined>; // string | number
// ReturnType - 获取函数返回类型
type T4 = ReturnType<() => string>; // string
// Parameters - 获取函数参数类型
type T5 = Parameters<(a: string, b: number) => void>; // [string, number]
配置和最佳实践
// tsconfig.json 推荐配置
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": ["ES2018", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"declaration": true,
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
// 类型声明文件示例 (types.d.ts)
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare global {
interface Window {
customProperty: string;
}
}
// 环境变量类型声明
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
API_URL: string;
DATABASE_URL: string;
}
}
从JavaScript迁移到TypeScript
📋 迁移步骤:
- 将
.js
文件重命名为.ts
- 添加基本的类型注解
- 启用严格模式选项
- 逐步消除any类型
- 添加接口和类型定义
- 利用高级类型特性
💡 最佳实践:
- 优先使用接口而不是类型别名来定义对象结构
- 使用严格模式配置
- 为公共API编写详细的类型定义
- 避免使用any类型,使用unknown替代
- 合理使用泛型提高代码复用性
- 利用类型保护进行运行时类型检查
⚠️ 常见陷阱:
- 过度使用any类型失去类型安全
- 忽略编译器错误和警告
- 类型定义过于复杂影响可读性
- 不正确的类型断言导致运行时错误
- 混用不同的模块系统
总结
TypeScript通过静态类型检查显著提升了JavaScript开发的安全性和效率。从基础类型到高级特性,TypeScript提供了丰富的工具来构建大型、可维护的应用程序。掌握TypeScript不仅能提高代码质量,还能改善开发体验,让重构和团队协作变得更加轻松。随着前端生态系统的发展,TypeScript已经成为现代JavaScript开发的标准选择。