Ts教程
· 阅读需 15 分钟
TypeScript 是微软开发的一个基于 JavaScript 的强类型编程语言,它添加了静态类型检查和其他高级特性,最终编译成纯 JavaScript 运行。
一、环境搭建与基础配置
1.1 安装 TypeScript
安装 TypeScript 有两种主要方式:
// 全局安装(推荐初学者)
npm install -g typescript
// 检查安装是否成功
tsc --version
// 或者在项目中作为开发依赖安装
npm init -y
npm install typescript --save-dev
1.2 创建第一个 TypeScript 文件
创建一个简单的 TypeScript 文件:
// hello.ts
function greet(name: string): string {
return `Hello, ${name}!`;
}
const message = greet("TypeScript");
console.log(message);
1.3 编译 TypeScript
使用 TypeScript 编译器将 .ts 文件编译为 .js 文件:
// 编译单个文件
tsc hello.ts
// 编译后生成 hello.js
// hello.js
function greet(name) {
return "Hello, " + name + "!";
}
var message = greet("TypeScript");
console.log(message);
// 自动编译和监听变化
tsc --watch hello.ts
// 或
tsc -w hello.ts
1.4 配置 tsconfig.json
创建 tsconfig.json 配置文件来管理编译选项:
// 生成默认配置文件
tsc --init
// 一个完整的 tsconfig.json 示例
{
"compilerOptions": {
"target": "ES2020", // 编译目标版本
"module": "commonjs", // 模块系统
"lib": ["ES2020", "DOM"], // 包含的库文件
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 源码目录
"strict": true, // 启用所有严格检查
"esModuleInterop": true, // 兼容 CommonJS 和 ES 模块
"skipLibCheck": true, // 跳过库文件检查
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"], // 包含的文件
"exclude": ["node_modules", "dist"] // 排除的文件
}
二、基础类型系统
2.1 JavaScript 的基础类型
// 布尔类型
let isDone: boolean = true;
// 数字类型:支持十进制、十六进制、二进制和八进制
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 tuple: [string, number];
tuple = ["hello", 10]; // OK
// tuple = [10, "hello"]; // Error
// 访问元组元素
console.log(tuple[0].substring(1)); // OK
// console.log(tuple[1].substring(1)); // Error: number 没有 substring 方法
// 枚举:给数字值取友好的名字
enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Green; // 1
// 也可以手动指定值
enum Status {
Success = 200,
NotFound = 404,
ServerError = 500
}
// 任意类型:可以赋予任何值(慎用)
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);
}
function infiniteLoop(): never {
while (true) {}
}
2.2 类型断言
类型断言告诉编译器"我知道这个值的类型":
// 类型断言的两种语法
let someValue: any = "this is a string";
// 尖括号语法
let strLength1: number = (<string>someValue).length;
// as 语法(在 JSX 中必须使用)
let strLength2: number = (someValue as string).length;
// 示例:处理来自 API 的数据
interface UserData {
name: string;
age: number;
}
function processData(data: any): void {
// 使用类型断言确保类型安全
const userData = data as UserData;
console.log(`User: ${userData.name}, Age: ${userData.age}`);
}
2.3 类型推断
TypeScript 会自动推断变量的类型:
// 类型推断示例
let x = 3; // x 被推断为 number
let y = "hello"; // y 被推断为 string
let z = [1, 2, 3]; // z 被推断为 number[]
// 最佳实践:让 TypeScript 推断简单类型
const isReady = true; // 推断为 true 字面量类型
const count = 10; // 推断为 10 字面量类型
// 复杂对象也能正确推断
const user = {
name: "Alice",
age: 30,
isAdmin: false
};
// user 被推断为 { name: string; age: number; isAdmin: boolean; }
三、函数
3.1 函数类型注解
// 完整的函数类型注解
function add(x: number, y: number): number {
return x + y;
}
// 函数表达式
const multiply = function(x: number, y: number): number {
return x * y;
};
// 箭头函数
const divide = (x: number, y: number): number => {
return x / y;
};
// 可选参数(必须放在必需参数后面)
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return `${firstName} ${lastName}`;
}
return firstName;
}
// 默认参数
function createUser(name: string, age: number = 18): string {
return `${name} is ${age} years old`;
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
// 函数重载
function processInput(input: string): string[];
function processInput(input: number): number;
function processInput(input: string | number): string[] | number {
if (typeof input === "string") {
return input.split("");
} else {
return input * 2;
}
}
// 调用重载函数
const result1 = processInput("hello"); // string[]
const result2 = processInput(42); // number
3.2 函数类型
// 函数类型定义
type MathOperation = (x: number, y: number) => number;
// 使用函数类型
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
// 回调函数类型
function fetchData(callback: (data: string) => void): void {
// 模拟异步操作
setTimeout(() => {
callback("Data received");
}, 1000);
}
// 调用带有回调的函数
fetchData((data) => {
console.log(data);
});
四、接口
4.1 对象接口
// 基础接口
interface Person {
name: string;
age: number;
email?: string; // 可选属性
readonly id: number; // 只读属性
}
// 使用接口
const alice: Person = {
name: "Alice",
age: 30,
id: 12345
};
// alice.id = 54321; // Error: 只读属性不能修改
// 函数类型接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: SearchFunc = function(src, sub) {
return src.search(sub) > -1;
};
// 可索引接口
interface StringArray {
[index: number]: string;
}
const myArray: StringArray = ["Bob", "Fred"];
const myStr: string = myArray[0];
4.2 接口继承
// 接口继承
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
const square: Square = {
color: "red",
sideLength: 10
};
// 多继承
interface PenStroke {
penWidth: number;
}
interface Square2 extends Shape, PenStroke {
sideLength: number;
}
const square2: Square2 = {
color: "blue",
sideLength: 5,
penWidth: 1.0
};
// 类实现接口
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) {}
}
五、类
5.1 类的基础
// 类的基本结构
class Animal {
// 属性
private name: string;
protected age: number; // 可以在子类中访问
public species: string;
// 静态属性
static classification: string = "Animalia";
// 构造函数
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.species = "Unknown";
}
// 方法
public move(distanceInMeters: number = 0): void {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
// 私有方法
private sleep(): void {
console.log(`${this.name} is sleeping`);
}
// 静态方法
static getClassification(): string {
return this.classification;
}
// Getter 和 Setter
get animalName(): string {
return this.name;
}
set animalName(newName: string) {
if (newName.length > 0) {
this.name = newName;
}
}
}
// 使用类
const dog = new Animal("Dog", 3);
dog.move(10);
console.log(Animal.getClassification());
dog.animalName = "Buddy";
console.log(dog.animalName);
5.2 继承
// 继承
class Bird extends Animal {
private wingSpan: number;
constructor(name: string, age: number, wingSpan: number) {
super(name, age); // 调用父类构造函数
this.species = "Bird";
this.wingSpan = wingSpan;
}
// 重写父类方法
public move(distanceInMeters: number = 0): void {
console.log(`${this.animalName} flew ${distanceInMeters}m.`);
super.move(distanceInMeters); // 调用父类方法
}
// 新增方法
public chirp(): void {
console.log("Chirp chirp!");
}
}
// 抽象类
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log("Department name: " + this.name);
}
abstract printMeeting(): void; // 必须在子类中实现
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing");
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
六、泛型
6.1 泛型基础
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>("myString");
let output2 = identity("myString"); // 类型推断
// 泛型数组
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
// 泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity2<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// loggingIdentity2(3); // Error: number 没有 length 属性
loggingIdentity2("hello"); // OK
loggingIdentity2([1, 2, 3]); // OK
// 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity2<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity2;
6.2 泛型类
// 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
constructor(zeroValue: T, add: (x: T, y: T) => T) {
this.zeroValue = zeroValue;
this.add = add;
}
}
let myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);
let stringNumeric = new GenericNumber<string>("", (x, y) => x + y);
// 使用多个泛型参数
class Pair<K, V> {
constructor(public key: K, public value: V) {}
getKey(): K {
return this.key;
}
getValue(): V {
return this.value;
}
}
const pair1 = new Pair<string, number>("age", 30);
const pair2 = new Pair<number, boolean>(1, true);
七、模块
7.1 导出和导入
// math.ts - 导出模块
export const PI = 3.14159;
export function calculateCircumference(diameter: number): number {
return PI * diameter;
}
export function calculateArea(radius: number): number {
return PI * radius * radius;
}
// 默认导出
export default class Calculator {
static add(a: number, b: number): number {
return a + b;
}
}
// app.ts - 导入模块
import Calculator, { PI, calculateCircumference } from './math';
// 重命名导入
import { PI as PiValue } from './math';
// 导入所有
import * as MathUtils from './math';
console.log(calculateCircumference(10));
console.log(Calculator.add(5, 3));
console.log(MathUtils.PI);
7.2 命名空间
// 命名空间
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();
// 测试
["Hello", "98052", "101"].forEach(s => {
for (let name in validators) {
console.log(`"${s}" - ${validators[name].isAcceptable(s) ? 'matches' : 'does not match'} ${name}`);
}
});
八、高级类型
8.1 联合类型和交叉类型
// 联合类型
type StringOrNumber = string | number;
function processInput(input: StringOrNumber): string {
if (typeof input === "string") {
return input.toUpperCase();
} else {
return input.toFixed(2);
}
}
// 交叉类型
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: string;
department: string;
}
type Staff = Person & Employee;
const staff: Staff = {
name: "Alice",
age: 30,
employeeId: "E123",
department: "Engineering"
};
// 字面量类型
type Direction = "north" | "south" | "east" | "west";
type StatusCode = 200 | 404 | 500;
function move(direction: Direction): void {
console.log(`Moving ${direction}`);
}
8.2 类型别名和映射类型
// 类型别名
type Point = {
x: number;
y: number;
};
type ID = string | number;
// 映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
id: number;
name: string;
email: string;
}
type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;
// 条件类型
type IsString<T> = T extends string ? true : false;
type Result1 = IsString<string>; // true
type Result2 = IsString<number>; // false
// 实用工具类型
type UserPreview = Pick<User, "name" | "email">;
type UserWithoutEmail = Omit<User, "email">;
type Nullable<T> = T | null;
type NonNullable<T> = T extends null | undefined ? never : T;
8.3 类型守卫
// typeof 类型守卫
function padLeft(value: string, padding: string | number): string {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
return padding + value;
}
// instanceof 类型守卫
class Bird {
fly() {
console.log("flying...");
}
}
class Fish {
swim() {
console.log("swimming...");
}
}
function move(pet: Bird | Fish) {
if (pet instanceof Bird) {
pet.fly();
} else {
pet.swim();
}
}
// 自定义类型守卫
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isDog(animal: Cat | Dog): animal is Dog {
return (animal as Dog).bark !== undefined;
}
function petAnimal(animal: Cat | Dog): void {
if (isDog(animal)) {
animal.bark();
} else {
animal.meow();
}
}
// in 操作符类型守卫
function move2(pet: Bird | Fish) {
if ("fly" in pet) {
pet.fly();
} else {
pet.swim();
}
}
九、实践项目示例
9.1 简单的待办事项应用
// types.ts
export interface Todo {
id: number;
title: string;
description: string;
completed: boolean;
createdAt: Date;
completedAt?: Date;
}
export type TodoFilter = 'all' | 'active' | 'completed';
// todoService.ts
export class TodoService {
private todos: Todo[] = [];
private nextId: number = 1;
addTodo(title: string, description: string = ''): Todo {
const todo: Todo = {
id: this.nextId++,
title,
description,
completed: false,
createdAt: new Date()
};
this.todos.push(todo);
return todo;
}
getTodos(filter: TodoFilter = 'all'): Todo[] {
switch (filter) {
case 'active':
return this.todos.filter(todo => !todo.completed);
case 'completed':
return this.todos.filter(todo => todo.completed);
default:
return [...this.todos];
}
}
toggleTodo(id: number): Todo | null {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
todo.completedAt = todo.completed ? new Date() : undefined;
return todo;
}
return null;
}
deleteTodo(id: number): boolean {
const index = this.todos.findIndex(t => t.id === id);
if (index !== -1) {
this.todos.splice(index, 1);
return true;
}
return false;
}
}
// app.ts
import { TodoService, TodoFilter } from './todoService';
const todoService = new TodoService();
// 添加待办事项
todoService.addTodo('Learn TypeScript', 'Complete the TypeScript tutorial');
todoService.addTodo('Build a project', 'Create a small project using TypeScript');
todoService.addTodo('Review code');
// 标记完成
todoService.toggleTodo(1);
// 获取不同状态的待办事项
console.log('All todos:', todoService.getTodos());
console.log('Active todos:', todoService.getTodos('active'));
console.log('Completed todos:', todoService.getTodos('completed'));
// 删除待办事项
todoService.deleteTodo(3);
十、调试和错误处理
10.1 常见的 TypeScript 错误
// 1. 类型不匹配错误
let age: number = "25"; // Error: Type 'string' is not assignable to type 'number'
// 2. 访问不存在的属性
interface User {
name: string;
}
const user: User = { name: "Alice" };
console.log(user.age); // Error: Property 'age' does not exist on type 'User'
// 3. 未定义的变量
function greet(name: string): void {
console.log(hello); // Error: Cannot find name 'hello'
}
// 4. 参数数量错误
function add(a: number, b: number): number {
return a + b;
}
add(1); // Error: Expected 2 arguments, but got 1
// 5. 模块导入错误
// import { nonExistent } from './module'; // Error: Module has no exported member 'nonExistent'
10.2 调试技巧
// 使用 source maps 进行调试
// tsconfig.json 中配置:
{
"compilerOptions": {
"sourceMap": true,
"outDir": "./dist"
}
}
// 使用 console.log 和类型断言
function processData(data: any): void {
// 调试时添加类型断言
const typedData = data as { id: number; name: string };
console.log('Processing data:', typedData);
// 检查类型
console.log('Type of data:', typeof data);
console.log('Is array?', Array.isArray(data));
}
// 使用调试器
function complexCalculation(values: number[]): number {
debugger; // 在此处设置断点
return values.reduce((total, value) => {
// 可以在此处检查变量
return total + value * 2;
}, 0);
}
十一、最佳实践
11.1 代码组织
// 良好的文件结构
project/
├── src/
│ ├── types/ // 类型定义
│ │ ├── user.ts
│ │ └── product.ts
│ ├── services/ // 业务逻辑
│ │ ├── userService.ts
│ │ └── productService.ts
│ ├── utils/ // 工具函数
│ │ └── formatters.ts
│ ├── components/ // UI 组件(如果使用框架)
│ └── index.ts // 入口文件
├── tests/ // 测试文件
├── dist/ // 编译输出
├── package.json
└── tsconfig.json
// 使用 barrel exports
// types/index.ts
export * from './user';
export * from './product';
export * from './order';
// 使用:import { User, Product } from './types';
11.2 类型安全的最佳实践
// 1. 避免使用 any
// 不好的做法
function processData(data: any): any {
return data.something;
}
// 好的做法
interface Data {
something: string;
}
function processData(data: Data): string {
return data.something;
}
// 2. 使用接口而不是内联类型
// 不好的做法
function createUser(user: { name: string; age: number }): void {
// ...
}
// 好的做法
interface User {
name: string;
age: number;
}
function createUser(user: User): void {
// ...
}
// 3. 使用 readonly 保护数据
interface Config {
readonly apiUrl: string;
readonly timeout: number;
}
const config: Config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
// config.apiUrl = 'new-url'; // Error: 只读属性
// 4. 使用 const 断言
const colors = ['red', 'green', 'blue'] as const;
// colors[0] = 'yellow'; // Error: 只读数组
// 5. 使用 unknown 而不是 any
function safeParse(json: string): unknown {
return JSON.parse(json);
}
const result = safeParse('{"name": "Alice"}');
// result.name; // Error: Object is of type 'unknown'
if (typeof result === 'object' && result !== null && 'name' in result) {
console.log((result as { name: string }).name); // 安全访问
}
十二、与 JavaScript 互操作
12.1 声明文件 (.d.ts)
// 为 JavaScript 库添加类型声明
// jquery.d.ts
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
function get(url: string, data?: any, success?: any, dataType?: any): void;
function post(url: string, data?: any, success?: any, dataType?: any): void;
}
// 使用 declare 声明全局变量
declare const VERSION: string;
declare function logMessage(message: string): void;
// 使用三斜线指令引用声明文件
/// <reference path="jquery.d.ts" />
// 在现代项目中,通常使用 @types 包
// npm install @types/jquery --save-dev
12.2 迁移 JavaScript 项目
// 1. 重命名 .js 文件为 .ts
// 2. 处理初始错误
let someValue; // 隐式的 any 类型
// 添加类型注解
let someValue: string;
// 3. 逐步添加严格的类型检查
// 从宽松配置开始
{
"compilerOptions": {
"strict": false,
"noImplicitAny": false
}
}
// 逐步启用严格模式
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
// 4. 使用 JSDoc 注解
/**
* @param {string} name - 用户名
* @returns {string} 问候语
*/
function greet(name) {
return `Hello, ${name}!`;
}
十三、学习资源和下一步
13.1 官方资源
13.2 推荐的学习路径
- 基础掌握:类型系统、接口、类、泛型
- 中级进阶:高级类型、模块、命名空间、装饰器
- 高级应用:类型编程、工具类型、编译器 API
- 框架集成:React + TypeScript、Vue + TypeScript、Angular
13.3 实战项目想法
- 待办事项应用:练习基础类型和状态管理
- API 客户端:练习接口定义和异步操作
- 计算器应用:练习类设计和错误处理
- 数据可视化工具:练习复杂类型和第三方库集成
记住,学习 TypeScript 是一个循序渐进的过程。从简单的类型注解开始,逐步掌握更高级的特性,最终你将能够写出类型安全、易于维护的 JavaScript 代码。
