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

📋 迁移步骤:

  1. .js文件重命名为.ts
  2. 添加基本的类型注解
  3. 启用严格模式选项
  4. 逐步消除any类型
  5. 添加接口和类型定义
  6. 利用高级类型特性

💡 最佳实践:

  • 优先使用接口而不是类型别名来定义对象结构
  • 使用严格模式配置
  • 为公共API编写详细的类型定义
  • 避免使用any类型,使用unknown替代
  • 合理使用泛型提高代码复用性
  • 利用类型保护进行运行时类型检查

⚠️ 常见陷阱:

  • 过度使用any类型失去类型安全
  • 忽略编译器错误和警告
  • 类型定义过于复杂影响可读性
  • 不正确的类型断言导致运行时错误
  • 混用不同的模块系统

总结

TypeScript通过静态类型检查显著提升了JavaScript开发的安全性和效率。从基础类型到高级特性,TypeScript提供了丰富的工具来构建大型、可维护的应用程序。掌握TypeScript不仅能提高代码质量,还能改善开发体验,让重构和团队协作变得更加轻松。随着前端生态系统的发展,TypeScript已经成为现代JavaScript开发的标准选择。

Next Post Previous Post
No Comment
Add Comment
comment url