零基础学 TypeScript:快速上手教程
引言
欢迎来到 TypeScript 的世界!如果你是 JavaScript 开发者,或者对编程有一定了解,并希望编写更健壮、更易维护的代码,那么 TypeScript 绝对值得你投入时间学习。
什么是 TypeScript?
TypeScript 是 JavaScript 的一个超集,它添加了静态类型定义。这意味着你可以在编写代码时指定变量、函数参数和返回值的类型。最终,TypeScript 代码会被编译成纯 JavaScript,因此它可以在任何支持 JavaScript 的地方运行。
为什么要学习 TypeScript?
- 更少的 Bug:静态类型检查能在代码运行前发现许多潜在错误,减少运行时 Bug。
- 更好的可维护性:类型定义让代码结构更清晰,易于理解和重构,尤其是在大型项目中。
- 增强的开发体验:现代 IDE(如 VS Code)能利用类型信息提供智能提示、自动补全和即时错误反馈,大大提高开发效率。
- 更好的团队协作:明确的类型约定有助于团队成员理解彼此的代码,减少沟通成本。
- 拥抱未来:TypeScript 已经成为前端和后端(Node.js)开发中越来越主流的技术。
安装与配置
开始学习 TypeScript 非常简单,你只需要 Node.js 和 npm(或 yarn)。
-
安装 Node.js 和 npm:
如果你的机器上还没有安装 Node.js,请前往 Node.js 官网 下载并安装。安装 Node.js 会同时安装 npm。 -
全局安装 TypeScript:
打开你的终端或命令行工具,运行以下命令:
bash
npm install -g typescript
验证安装是否成功:
bash
tsc -v
这会显示 TypeScript 编译器的版本号。 -
创建你的第一个 TypeScript 项目:
新建一个文件夹,进入该文件夹,然后初始化一个package.json文件:
bash
mkdir my-ts-project
cd my-ts-project
npm init -y
安装 TypeScript 作为开发依赖:
bash
npm install --save-dev typescript
生成 TypeScript 配置文件tsconfig.json:
bash
npx tsc --init
tsconfig.json是 TypeScript 项目的核心配置文件,你可以根据项目需求调整其中的选项,例如编译目标(target)、模块系统(module)等。对于初学者,默认配置通常就足够了。
基础语法
类型注解 (Type Annotations)
TypeScript 最核心的特性就是类型注解。你可以在变量、函数参数和函数返回值后面使用 : 来指定类型。
“`typescript
// 变量类型注解
let age: number = 30;
let name: string = “Alice”;
let isStudent: boolean = false;
// 函数参数类型注解
function greet(personName: string) {
console.log(Hello, ${personName}!);
}
greet(name); // Hello, Alice!
// 函数返回值类型注解
function add(a: number, b: number): number {
return a + b;
}
let sum: number = add(5, 3); // sum will be 8
“`
基本数据类型 (Primitive Types)
TypeScript 支持所有 JavaScript 的基本数据类型,并为它们提供了类型注解。
number:所有数字,包括整数和浮点数。
typescript
let price: number = 99.99;string:所有字符串。
typescript
let message: string = "Hello TypeScript!";boolean:true或false。
typescript
let isActive: boolean = true;null和undefined:在严格模式下,它们只能被赋值给各自的类型或any类型。
typescript
let n: null = null;
let u: undefined = undefined;symbol:ES6 中新增的原始类型,表示独一无二的值。
typescript
const sym: symbol = Symbol('key');bigint:ES2020 中新增的原始类型,用于处理大整数。
typescript
let bigNumber: bigint = 100n;
数组 (Arrays)
定义数组有两种方式:
类型[]:
typescript
let numbers: number[] = [1, 2, 3, 4];
let names: string[] = ["Alice", "Bob"];Array<类型>(泛型数组):
typescript
let scores: Array<number> = [90, 85, 95];
元组 (Tuples)
元组表示一个已知元素数量和类型的数组,各元素的类型不必相同。
typescript
// 定义一个元组,第一个元素是字符串,第二个是数字
let user: [string, number];
user = ["Alice", 25]; // 正确
// user = [25, "Alice"]; // 错误:类型顺序不匹配
// user = ["Bob", 30, true]; // 错误:元素数量不匹配
枚举 (Enums)
枚举允许你定义一组命名的常量。
“`typescript
enum Color {
Red, // 默认为 0
Green, // 默认为 1
Blue // 默认为 2
}
let c: Color = Color.Green; // c 的值为 1
console.log(Color[1]); // 输出: Green
// 你也可以手动赋值
enum StatusCode {
Success = 200,
NotFound = 404,
ServerError = 500
}
let status: StatusCode = StatusCode.Success; // status 的值为 200
“`
Any, Unknown, Void, Never
any:表示可以是任何类型。当你不确定某个变量的类型时,可以使用any。但过度使用any会失去 TypeScript 的类型优势。
typescript
let someValue: any = "this is a string";
someValue = 100; // 可以重新赋值为其他类型
someValue.toFixed(); // 不会报错,但运行时可能出错unknown:与any类似,但更安全。unknown类型的值不能直接进行操作,必须先进行类型检查。
typescript
let unknownValue: unknown = "hello";
// unknownValue.toUpperCase(); // 错误:对象是 'unknown'
if (typeof unknownValue === 'string') {
console.log(unknownValue.toUpperCase()); // 正确:现在知道是字符串了
}void:表示没有任何类型。通常用于函数没有返回值的情况。
typescript
function warnUser(): void {
console.log("This is a warning message.");
}-
never:表示那些永远不会返回的函数,或者总是抛出异常的函数。
“`typescript
function error(message: string): never {
throw new Error(message);
}function infiniteLoop(): never {
while (true) {}
}
“`
函数 (Functions)
TypeScript 为函数增加了类型检查的能力。
函数类型 (Function Types)
可以为函数定义类型,包括参数类型和返回值类型。
“`typescript
// 普通函数定义
function multiply(x: number, y: number): number {
return x * y;
}
// 箭头函数定义
const subtract = (x: number, y: number): number => {
return x – y;
};
// 定义函数类型
let myOperation: (arg1: number, arg2: number) => number;
myOperation = multiply; // 正确
// myOperation = subtract; // 也正确
console.log(myOperation(10, 5)); // 50
“`
可选参数与默认参数 (Optional and Default Parameters)
-
可选参数:使用
?标记参数为可选。可选参数必须在所有必选参数之后。
“`typescript
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + ” ” + lastName;
} else {
return firstName;
}
}console.log(buildName(“Bob”)); // Bob
console.log(buildName(“Bob”, “Adams”)); // Bob Adams
* **默认参数**:为参数提供默认值。typescript
function showMessage(text: string, sender: string = “System”): void {
console.log(${sender}: ${text});
}showMessage(“Hello there!”); // System: Hello there!
showMessage(“How are you?”, “Alice”); // Alice: How are you?
“`
函数重载 (Function Overloading)
函数重载允许你为同一个函数提供多个函数签名,以处理不同类型的输入。
“`typescript
// 重载签名 (只提供类型,没有实现)
function combine(a: number, b: number): number;
function combine(a: string, b: string): string;
function combine(a: string, b: number): string; // 新增重载签名
// 实现签名 (必须兼容所有重载签名)
function combine(a: any, b: any): any {
if (typeof a === ‘string’ || typeof b === ‘string’) {
return a.toString() + b.toString();
}
return a + b;
}
console.log(combine(1, 2)); // 3 (number)
console.log(combine(“Hello”, “World”)); // HelloWorld (string)
console.log(combine(“Age: “, 30)); // Age: 30 (string)
“`
接口 (Interfaces)
接口是 TypeScript 中定义对象结构的重要方式,它只关注值的外形,也就是结构化类型系统(或鸭式辨型法 “duck typing”)的核心原则。
定义对象形状 (Defining Object Shapes)
“`typescript
interface Point {
x: number;
y: number;
}
function printPoint(p: Point): void {
console.log(X: ${p.x}, Y: ${p.y});
}
let p1 = { x: 10, y: 20 };
printPoint(p1); // 正确
let p2 = { x: 10, y: 20, z: 30 }; // 多余属性在某些情况下也是允许的,因为它至少满足了 Point 接口的必要属性
printPoint(p2); // 正确
// let p3 = { x: 10 }; // 错误:缺少属性 ‘y’
// printPoint(p3);
“`
可选属性 (Optional Properties)
接口中的属性也可以是可选的,使用 ? 标记。
“`typescript
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” });
let mySquare2 = createSquare({});
“`
只读属性 (Readonly Properties)
使用 readonly 关键字来指定属性为只读,在对象创建后就不能再修改。
“`typescript
interface ReadonlyPoint {
readonly x: number;
readonly y: number;
}
let rp: ReadonlyPoint = { x: 10, y: 20 };
// rp.x = 5; // 错误:Cannot assign to ‘x’ because it is a read-only property.
“`
函数类型接口 (Function Type Interfaces)
接口也可以描述函数类型。
“`typescript
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
}
console.log(mySearch(“hello world”, “world”)); // true
“`
可索引类型接口 (Indexable Type Interfaces)
索引签名描述了那些可以通过索引访问的类型,比如 a[10] 或 a["foo"]。
“`typescript
interface StringArray {
[index: number]: string; // 索引签名:索引是数字,值是字符串
}
let myArray: StringArray;
myArray = [“Bob”, “Fred”];
let firstItem: string = myArray[0]; // firstItem 的类型是 string
“`
类 (Classes)
TypeScript 完全支持 ES6 的类,并在此基础上增加了类型检查和修饰符。
基本用法 (Basic Usage)
“`typescript
class Greeter {
greeting: string; // 属性
constructor(message: string) { // 构造函数
this.greeting = message;
}
greet(): string { // 方法
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter(“world”);
console.log(greeter.greet()); // Hello, world
“`
继承 (Inheritance)
使用 extends 关键字实现继承。
``typescript${this.name} moved ${distanceInMeters}m.`);
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log(“Slithering…”);
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log(“Galloping…”);
super.move(distanceInMeters);
}
}
let sam = new Snake(“Sammy the Python”);
let tom: Animal = new Horse(“Tommy the Palomino”); // 多态
sam.move(); // Slithering… Sammy the Python moved 5m.
tom.move(34); // Galloping… Tommy the Palomino moved 34m.
“`
修饰符 (Access Modifiers)
TypeScript 支持 public、private 和 protected 三种访问修饰符。
public(默认):在任何地方都可以访问。private:只能在声明它的类内部访问。protected:可以在声明它的类内部和子类中访问。
“`typescript
class Person {
public name: string; // 默认为 public
private age: number;
protected city: string;
constructor(name: string, age: number, city: string) {
this.name = name;
this.age = age;
this.city = city;
}
public getAge(): number {
return this.age; // 可以在类内部访问 private 属性
}
}
class Employee extends Person {
constructor(name: string, age: number, city: string, department: string) {
super(name, age, city);
this.department = department;
}
department: string;
public getCity(): string {
return this.city; // 可以在子类内部访问 protected 属性
}
}
let john = new Person(“John”, 30, “New York”);
console.log(john.name); // John (public)
// console.log(john.age); // 错误:’age’ 是 private 属性
console.log(john.getAge()); // 30 (通过 public 方法访问)
let jane = new Employee(“Jane”, 25, “London”, “HR”);
console.log(jane.getCity()); // London (通过子类方法访问 protected 属性)
// console.log(jane.city); // 错误:’city’ 是 protected 属性,只能在类或子类内部访问
“`
抽象类 (Abstract Classes)
抽象类不能直接实例化,只能被继承。它们可以包含抽象方法(不包含具体实现)和具体方法。
“`typescript
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...");
}
}
// let department = new Department(); // 错误:不能创建抽象类的实例
let accounting = new AccountingDepartment();
accounting.printName(); // Department name: Accounting and Auditing
accounting.printMeeting(); // The Accounting Department meets each Monday at 10am.
accounting.generateReports(); // Generating accounting reports…
“`
泛型 (Generics)
泛型允许你编写可重用的代码,而不需要提前知道所有类型。它们使得代码在处理多种数据类型时保持灵活性和类型安全。
泛型函数 (Generic Functions)
“`typescript
function identity
return arg;
}
let output1 = identity
let output2 = identity(123); // 类型推断,output2 的类型是 number
“`
泛型接口 (Generic Interfaces)
“`typescript
interface GenericIdentityFn
(arg: T): T;
}
function identityFn
return arg;
}
let myIdentity: GenericIdentityFn
console.log(myIdentity(42)); // 42
“`
泛型类 (Generic Classes)
“`typescript
class GenericNumber
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 10)); // 10
let stringNumeric = new GenericNumber
stringNumeric.zeroValue = “”;
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, “test”)); // test
“`
类型推断与类型兼容性 (Type Inference and Type Compatibility)
类型推断 (Type Inference)
TypeScript 在很多情况下能够自动推断出变量的类型,而无需显式注解。
“`typescript
let x = 3; // x 被推断为 number 类型
// x = “hello”; // 错误:不能将类型 ‘string’ 分配给类型 ‘number’
let arr = [0, 1, null]; // arr 被推断为 (number | null)[]
“`
类型兼容性 (Type Compatibility)
TypeScript 的类型兼容性基于结构化类型系统(或称为鸭式辨型法)。如果两个类型具有相同的结构,那么它们就是兼容的。
“`typescript
interface Named {
name: string;
}
class PersonType {
name: string;
}
let p: Named;
// ok, because of structural typing
p = new PersonType(); // PersonType 实例兼容 Named 接口,因为它们都有一个 ‘name’ 属性
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
let p2d: Point2D = { x: 1, y: 2 };
let p3d: Point3D = { x: 1, y: 2, z: 3 };
p2d = p3d; // ok, p3d 至少包含了 p2d 的所有属性
// p3d = p2d; // error, p2d 缺少属性 ‘z’
“`
进阶概念 (Advanced Concepts – 简要介绍)
- 类型断言 (Type Assertions):当你比 TypeScript 更清楚某个变量的类型时,可以使用类型断言。
typescript
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length; // 方式一
let strLength2: number = (someValue as string).length; // 方式二(JSX 推荐) - 联合类型 (Union Types):表示一个值可以是几种类型之一。
typescript
let id: number | string;
id = 101;
id = "abc";
// id = true; // 错误 - 交叉类型 (Intersection Types):将多个类型合并为一个类型,它拥有所有类型的特性。
typescript
interface Person {
name: string;
}
interface Developer {
code: () => void;
}
type FullStackDeveloper = Person & Developer; // 同时具备 Person 和 Developer 的所有属性和方法 - 模块 (Modules):使用
export和import来组织和重用代码。
实战演练 (Practical Exercise)
让我们创建一个简单的 TypeScript 文件并编译它。
-
在
my-ts-project文件夹中创建一个新文件src/app.ts:
“`typescript
// src/app.ts
interface User {
id: number;
name: string;
email?: string;
}function getUserInfo(user: User): string {
const emailInfo = user.email ?(Email: ${user.email}): ”;
returnUser ID: ${user.id}, Name: ${user.name}${emailInfo};
}const alice: User = {
id: 1,
name: “Alice”,
email: “[email protected]”
};const bob: User = {
id: 2,
name: “Bob”
};console.log(getUserInfo(alice));
console.log(getUserInfo(bob));
“` -
在终端中,运行 TypeScript 编译器:
bash
npx tsc
这会在你的my-ts-project目录下生成一个app.js文件(如果tsconfig.json配置了outDir,则会在outDir指定的目录下生成)。 -
运行编译后的 JavaScript 文件:
bash
node app.js
你将看到输出:
User ID: 1, Name: Alice (Email: [email protected])
User ID: 2, Name: Bob
恭喜!你已经成功编写并运行了你的第一个 TypeScript 代码。
总结
本教程带你从零开始,快速上手了 TypeScript 的核心概念,包括:
- 类型注解:让代码更具可读性和健壮性。
- 基本数据类型、数组、元组、枚举:构建数据的基石。
- 函数:定义带类型检查的函数。
- 接口:描述对象结构。
- 类:面向对象编程。
- 泛型:编写灵活且类型安全的代码。
- 类型推断与兼容性:TypeScript 的智能之处。
这只是 TypeScript 的冰山一角。要深入学习,你可以探索更多高级类型(如条件类型、映射类型)、装饰器、JSX/TSX 支持、以及与流行框架(如 React, Angular, Vue)的集成。
接下来做什么?
- 阅读官方文档:TypeScript 官网 提供了最全面和最新的信息。
- 尝试小型项目:将 TypeScript 应用到你的下一个个人项目中。
- 学习流行框架的 TypeScript 用法:如果你在使用 React, Angular 或 Vue,学习它们如何与 TypeScript 结合使用。
- 参与社区:加入 TypeScript 相关的社区,提问和分享经验。
祝你在 TypeScript 的学习旅程中一切顺利!