为什么要学习TypeScript
JavaScript(JS)和TypeScript(TS)都是用于构建前端和后端应用程序的编程语言,但它们在一些方面有所不同。
-
类型系统:
- JavaScript: JavaScript是一种动态类型语言,变量的类型是在运行时确定的。这意味着无需在声明变量时指定其类型。
- TypeScript: TypeScript是JavaScript的超集,它引入了静态类型系统。在TypeScript中,可以在编码阶段指定变量的类型,从而提供更早的错误检查和代码提示。
-
类型注解:
- JavaScript: JavaScript没有原生的类型注解,变量类型通常由赋值操作决定。
- TypeScript: TypeScript支持类型注解,可以使用类型来明确变量的类型。
-
编译:
- JavaScript: JavaScript代码在运行之前不需要编译,可以直接在浏览器或Node.js环境中执行。
- TypeScript: TypeScript代码需要被编译成JavaScript代码,以便在浏览器或Node.js环境中运行。TypeScript的编译过程将类型注解转换为普通的JavaScript代码。
-
代码提示和自动补全:
- JavaScript: JavaScript的代码提示和自动补全有限,通常依赖于编辑器的基本功能。
- TypeScript: TypeScript具有强大的代码提示和自动补全功能,因为它在编译时了解变量的类型。
-
类型安全性:
- JavaScript: 由于动态类型的特性,JavaScript在运行时可能会出现类型相关的错误。
- TypeScript: TypeScript的静态类型系统可以在编码阶段捕获类型错误,从而减少运行时错误的风险。
-
生态系统:
- JavaScript: JavaScript是一种广泛使用的编程语言,具有大量的开源库和框架。
- TypeScript: TypeScript在现代Web开发中越来越受欢迎,许多流行的框架和库都提供了TypeScript的支持。
-
学习曲线:
- JavaScript: JavaScript是一种相对容易学习的语言,适合初学者入门。
- TypeScript: TypeScript相对于JavaScript来说需要更多的学习,特别是对于静态类型和类型注解的概念。
-
适用场景:
- JavaScript: 适用于快速原型开发、小型项目以及前端开发。
- TypeScript: 适用于大型项目、团队协作以及需要更强的类型安全性的场景。
TypeScript基础
基础类型
-
number: 用于表示数值类型,包括整数和浮点数。
示例:
let age: number = 25; let price: number = 59.99; -
string: 用于表示字符串类型,可以包含文本和字符。
示例:
let name: string = "John"; let message: string = "Hello, TypeScript!"; -
boolean: 用于表示布尔类型,可以取
true或false。示例:
let isActive: boolean = true; let isStudent: boolean = false; -
array: 用于表示数组,可以包含相同类型的多个元素。
示例:
let numbers: number[] = [1, 2, 3, 4, 5]; let fruits: string[] = ["apple", "banana", "orange"]; -
tuple: 用于表示固定长度和类型的数组。
示例:
let person: [string, number] = ["John", 30]; -
enum: 用于创建一组命名的常量值。
示例:
enum Color { Red, Green, Blue, } let selectedColor: Color = Color.Green; -
any: 用于表示任意类型,不进行类型检查。
示例:
let dynamicValue: any = "Hello, TypeScript!"; dynamicValue = 42; -
void: 用于表示没有返回值的函数。
示例:
function logMessage(message: string): void { console.log(message); } -
null 和 undefined: 用于表示空值或未定义的值。
示例:
let emptyValue: null = null; let notDefined: undefined = undefined; -
never: 用于表示永远不会返回结果的函数类型,或者表示抛出异常或进入无限循环的情况。
示例:
function throwError(message: string): never { throw new Error(message); } -
object: 用于表示非原始类型(即除number、string、boolean、symbol、null、undefined之外的类型)。
示例:
let person: object = { name: "John", age: 25 }; -
unknown: 类似于
any,但更安全。需要进行类型检查或类型断言。示例:
let userInput: unknown = "Hello, TypeScript!"; if (typeof userInput === "string") { let upperCaseValue: string = userInput.toUpperCase(); }
函数类型
TS定义函数类型时要定义输入参数和输出类型。以下为函数的使用方法:
-
函数声明:
示例:
function add(x: number, y: number): number { return x + y; } -
函数表达式:
示例:
const subtract = function(x: number, y: number): number { return x - y; }; -
箭头函数:
示例:
const multiply = (x: number, y: number): number => x * y; -
函数类型注解:
示例:
let calculate: (x: number, y: number) => number; calculate = add; // 可以赋值为具有相同签名的函数 -
可选参数和默认参数:
示例:
function greet(name: string, greeting?: string): string { if (greeting) { return `${greeting}, ${name}!`; } else { return `Hello, ${name}!`; } } -
剩余参数:
示例:
function sumAll(...numbers: number[]): number { return numbers.reduce((total, num) => total + num, 0); } -
重载: 名称相同但参数不同,可以通过重载支持多种类型。
示例:
function processValue(input: string): string; function processValue(input: number): number; function processValue(input: any): any { if (typeof input === 'string') { return input.toUpperCase(); } else if (typeof input === 'number') { return input * 2; } return input; } -
回调函数:
示例:
function fetchData(url: string, callback: (data: any) => void): void { // 调用网络请求,获取数据 const data = ...; // 假设获得了数据 callback(data); // 调用回调函数处理数据 } fetchData('https://example.com/api', (response) => { console.log(response); }); -
函数类型接口:
示例:
interface BinaryOperation { (x: number, y: number): number; } const divide: BinaryOperation = (x, y) => x / y;
类的写法
在TypeScript中,类的写法与JavaScript类似,但它增加了类型注解和类型检查,使得类的使用更加类型安全。以下是TypeScript中类的写法示例,并与JavaScript进行了比较:
TypeScript类的写法和示例:
class Person {
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const john = new Person("John", 30);
john.greet();
在上述示例中,Person类具有私有属性name和age,并且包含构造函数和方法greet。
JavaScript中类的写法和示例:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const john = new Person("John", 30);
john.greet();
在JavaScript中,类的写法与TypeScript类似,但没有类型注解和类型检查的支持。属性和方法可以随意访问,没有访问修饰符的概念。
TypeScript与JavaScript类的不同点:
- 类型注解: 在TypeScript中,可以为属性和方法添加类型注解,从而明确指定它们的数据类型。
- 访问修饰符: TypeScript引入了访问修饰符(
private、protected、public)来限制属性和方法的访问范围,增加了封装性。 - 类型安全性: TypeScript的类在编译时进行类型检查,可以捕获许多常见的错误,提高代码的稳定性。
- 构造函数参数属性: TypeScript提供了简化构造函数参数赋值的语法,可以在构造函数参数中直接声明属性,从而减少重复代码。
- 继承和实现接口: TypeScript的类支持类之间的继承,以及实现接口来强制定义特定的属性和方法。
- 静态属性和方法: TypeScript引入了静态属性和方法的概念,这些属性和方法属于类本身而不是实例。
下面是 TypeScript 中各种类型的类的写法示例:
-
普通类:
普通类用于创建对象,可以包含属性和方法。
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const john = new Person("John", 30);
john.greet();
-
继承类:
继承类可以继承另一个类的属性和方法,并可以添加自己的属性和方法。
class Student extends Person {
studentId: number;
constructor(name: string, age: number, studentId: number) {
super(name, age);
this.studentId = studentId;
}
study() {
console.log(`${this.name} is studying.`);
}
}
const alice = new Student("Alice", 25, 12345);
alice.greet();
alice.study();
-
抽象类:
抽象类用于定义基础结构,可以包含抽象方法和具体方法,不能被实例化,只能被继承。
abstract class Shape {
abstract getArea(): number;
displayArea() {
const area = this.getArea();
console.log(`Area: ${area}`);
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
const circle = new Circle(5);
circle.displayArea();
-
接口继承类:
可以使用接口来扩展类的类型,使对象符合特定的接口。
class Car {
constructor(public brand: string) {}
}
interface ElectricCar extends Car {
batteryCapacity: number;
}
const tesla: ElectricCar = {
brand: "Tesla",
batteryCapacity: 85,
};
-
构造函数签名:
使用构造函数签名可以定义一个构造函数类型,用于描述类的实例化过程。
interface Point {
x: number;
y: number;
}
class PointClass implements Point {
constructor(public x: number, public y: number) {}
}
const point: Point = new PointClass(3, 4);
这些示例涵盖了 TypeScript 中各种类型的类的写法,从普通类到继承类和抽象类,以及如何使用接口来扩展类的类型。这些特性使得 TypeScript 的类能够更加灵活和类型安全。
TypeScript进阶
高级类型
-
泛型(Generics):泛型允许编写可以适用于多种类型的可重用代码。通过使用泛型,可以在编译时指定要操作的类型。泛型在 TypeScript 中的应用场景很多,以下是一些常见的应用场景和示例:
- 函数参数的灵活性:当函数需要处理不同类型的参数时,可以使用泛型来增加函数的灵活性和复用性。例如,一个通用的
identity函数可以返回传入的参数,而不改变其类型:
function identity<T>(arg: T): T { return arg; } let result1 = identity<string>("Hello"); // result1 的类型为 string let result2 = identity<number>(42); // result2 的类型为 number- 数据结构的扩展性:当需要创建可扩展的数据结构时,泛型非常有用。例如,使用泛型来定义一个可存储不同类型数据的数组:
let array: Array<number> = [1, 2, 3]; // 数组中的元素类型为 number- 类的复用性和类型安全:在类定义中使用泛型可以提高代码的复用性和类型安全性。例如,一个通用的
Pair类可以存储一对值,并保持它们的类型一致:
class Pair<T, U> { private first: T; private second: U; constructor(first: T, second: U) { this.first = first; this.second = second; } getFirst(): T { return this.first; } getSecond(): U { return this.second; } } let pair = new Pair<string, number>("Hello", 42); console.log(pair.getFirst()); // 输出:Hello console.log(pair.getSecond()); // 输出:42泛型的基本定义如下:
function functionName<T>(arg: T): ReturnType { // 函数体 }其中:
<T>表示泛型参数的占位符,可以是任何有效的标识符。arg: T表示函数的参数类型为泛型参数T。ReturnType表示函数的返回类型,根据实际情况进行替换。
通过泛型,可以在编写函数或类时保持灵活性,以便在不同的上下文中使用不同的类型,并提高代码的复用性和类型安全性。
- 函数参数的灵活性:当函数需要处理不同类型的参数时,可以使用泛型来增加函数的灵活性和复用性。例如,一个通用的
-
联合类型(Union Types):联合类型允许将多个类型组合在一起。变量具有联合类型时,可以赋值给其中任何一个类型。例如:
let value: string | number;
value = "hello";
value = 42;
- 交叉类型(Intersection Types):交叉类型允许将多个类型合并成一个新类型。新类型将具有所有原始类型的特性。例如:
interface A {
propA: number;
}
interface B {
propB: string;
}
type C = A & B;
let obj: C = {
propA: 42,
propB: "hello",
};
- 类型别名(Type Aliases):类型别名允许为现有类型创建一个新名称。这在复杂类型或重复使用的类型上特别有用。例如:
type Point = {
x: number;
y: number;
};
let p: Point = { x: 10, y: 20 };
- 条件类型(Conditional Types):条件类型允许根据条件选择类型。通过使用条件类型,可以根据某些条件确定要返回的类型。例如:
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
"unknown";
let name: TypeName<string>; // 类型为 "string"
let age: TypeName<number>; // 类型为 "number"
- 映射类型(Mapped Types):映射类型允许根据现有类型创建新类型,并根据需要修改其属性。通过使用映射类型,可以自动创建具有相同结构但具有不同类型的新类型。例如:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
let readonlyPerson: Readonly<Person> = {
name: "Alice",
age: 25,
};
readonlyPerson.name = "Bob"; // 编译错误,属性为只读
泛型工具类型
在 TypeScript 中,有一些内置的泛型工具类型可用于操作和转换其他类型。以下是一些常用的泛型工具类型:
- Partial: 创建一个类型,其中所有属性都是可选的。它接受一个泛型参数
T,并将T中的所有属性设置为可选属性。示例:
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person>;
/*
PartialPerson 的类型为:
{
name?: string;
age?: number;
}
*/
- Readonly: 创建一个类型,其中所有属性都是只读的。它接受一个泛型参数
T,并将T中的所有属性设置为只读属性。示例:
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
/*
ReadonlyPerson 的类型为:
{
readonly name: string;
readonly age: number;
}
*/
- Pick<T, K>: 从类型
T中选择指定的属性K,创建一个新类型。它接受两个泛型参数T和K,其中T是源类型,K是要选择的属性名的联合类型。示例:
interface Person {
name: string;
age: number;
address: string;
}
type PersonInfo = Pick<Person, 'name' | 'age'>;
/*
PersonInfo 的类型为:
{
name: string;
age: number;
}
*/
- Omit<T, K>: 从类型
T中排除指定的属性K,创建一个新类型。它接受两个泛型参数T和K,其中T是源类型,K是要排除的属性名的联合类型。示例:
interface Person {
name: string;
age: number;
address: string;
}
type PersonWithoutAddress = Omit<Person, 'address'>;
/*
PersonWithoutAddress 的类型为:
{
name: string;
age: number;
}
*/
- Exclude<T, U>: 从类型
T中排除可以赋值给类型U的部分,创建一个新类型。它接受两个泛型参数T和U,并返回T中不包含在U中的类型。示例:
type Numbers = 1 | 2 | 3 | 4 | 5;
type EvenNumbers = Exclude<Numbers, 1 | 3 | 5>; // EvenNumbers 的类型为 2 | 4
这些是 TypeScript 中的一些常用泛型工具类型,它们提供了方便的方式来操作和转换类型,以满足特定的需求。还有其他许多泛型工具类型可供使用,可以根据具体的情况选择适合的工具类型。