前言
TypeScript是一种由Microsoft开发的编程语言,它是JavaScript的超集,意味着它可以编写与JavaScript完全兼容的代码,并且可以扩展其功能。TypeScript的主要目标是提供类型安全性和更好的可维护性,使得开发大型复杂应用程序更加容易。在本文中,我们将介绍入门TypeScript的基础知识。
安装TypeScript
首先,我们需要安装TypeScript。可以使用npm包管理器全局安装TypeScript:
css
bashCopy code
npm install -g typescript
安装完成后,可以通过输入以下命令来检查是否安装成功:
css
bashCopy code
tsc --version
编写第一个TypeScript程序
让我们从一个简单的"Hello, World!"程序开始。创建一个名为hello.ts
的文件,然后在其中编写以下代码:
javascript
typescriptCopy code
function sayHello(name: string) {
console.log(`Hello, ${name}!`);
}
sayHello("TypeScript");
保存并退出文件。然后使用以下命令将其编译为JavaScript:
css
bashCopy code
tsc hello.ts
执行上述命令后,将生成一个名为hello.js
的文件。要运行这个程序,可以在命令行中输入以下命令:
css
bashCopy code
node hello.js
输出应该是:
css
bashCopy code
Hello, TypeScript!
在上面的代码中,我们定义了一个名为sayHello
的函数,它有一个参数name
,类型为字符串。在函数体内,我们使用console.log
打印出"Hello, "和name
的值,并使用模板字面量将它们组合在一起。最后,我们调用这个函数并将参数设置为"TypeScript"。
类型注释
TypeScript的主要特点之一是提供类型注释,可以在编译时检查类型错误。在上面的示例中,我们定义了一个name
参数的类型为字符串,这是通过在函数声明中使用冒号和类型名称来实现的。
TypeScript支持多种类型注释,包括:
- 基本类型:如数字、字符串、布尔值、null、undefined、void等。
- 对象类型:如数组、元组、枚举、类等。
- 函数类型:如函数声明、函数表达式、箭头函数等。
例如,以下是一个函数声明的完整类型注释:
typescript
typescriptCopy code
function add(x: number, y: number): number {
return x + y;
}
在这个示例中,我们定义了两个参数x
和y
,它们的类型都是数字,并且函数返回值的类型也是数字。
元组
元组是一种特殊的数组类型,它可以存储多个类型不同的值。在 TypeScript 中,我们可以使用元组来表示一组有序的值。
下面是一个简单的示例:
arduino
typescriptCopy code
let tuple: [string, number, boolean] = ["hello", 42, true];
console.log(tuple[0]); // "hello"
console.log(tuple[1]); // 42
console.log(tuple[2]); // true
在这个示例中,我们定义了一个名为tuple
的元组,它具有三个元素,分别是string
、number
和boolean
类型的值。我们使用索引访问元组中的元素。
枚举
TypeScript支持枚举,它是一组具有名称的常量。下面是一个简单的示例:
ini
typescriptCopy code
enum Color {
Red,
Green,
Blue
}
let color: Color = Color.Red;
console.log(color); // 0
let colorName: string = Color[1];
console.log(colorName); // "Green"
在这个示例中,我们定义了一个名为Color
的枚举,它有三个值:Red
、Green
和Blue
,它们分别对应0、1和2。我们可以使用枚举的值来声明变量,或使用枚举的名称和值之间的映射来获取名称。
泛型
TypeScript支持泛型,它允许我们编写可重用的代码,可以适用于多种类型。下面是一个简单的示例:
sql
typescriptCopy code
function identity<T>(arg: T): T {
return arg;
}
let result = identity<string>("Hello");
console.log(result); // "Hello"
在这个示例中,我们定义了一个名为identity
的函数,它接受一个类型为T
的参数,并返回同样的类型。我们使用了类型参数来指定函数应该适用于哪种类型。
接口
接口类型是一种抽象的数据类型,用于描述对象的形状和结构,可以被用作对象的类型注解,以确保对象的属性和方法符合指定的形状。
下面是一个简单的接口类型的示例:
css
typescriptCopy code
interface Person {
name: string;
age: number;
}
上述代码定义了一个名为 Person
的接口类型,它有两个属性 name
和 age
,分别表示人的姓名和年龄。在 TypeScript 中,我们可以使用这个接口类型来定义一个符合该接口类型要求的对象:
css
typescriptCopy code
const person: Person = { name: "Tom", age: 18 };
上述代码定义了一个 person
对象,它符合 Person
接口类型的要求,因为它有 name
和 age
两个属性,分别为字符串和数字类型。
使用接口类型可以提高代码的可读性和可维护性,因为它能够明确指定对象的属性和方法的类型和结构。
类型别名
类型别名是一种给类型起别名的方式,用于提高代码的可读性和可维护性。在 TypeScript 中,我们可以使用type
关键字来定义类型别名。
下面是一个简单的示例:
ini
typescriptCopy code
type Point = {
x: number;
y: number;
};
function distance(p1: Point, p2: Point) {
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
return Math.sqrt(dx * dx + dy * dy);
}
const p1: Point = { x: 0, y: 0 };
const p2: Point = { x: 3, y: 4 };
console.log(distance(p1, p2)); // 5
在这个示例中,我们定义了一个名为Point
的类型别名,它表示一个具有x
和y
属性的点。我们定义了一个distance
函数,它接受两个Point
类型的参数,并返回它们之间的距离。我们创建了两个Point
对象,并计算它们之间的距离。
命名空间
TypeScript支持命名空间,它允许我们将相关的代码组织在一起,并避免名称冲突。下面是一个简单的示例:
arduino
typescriptCopy code
namespace Shapes {
export class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
}
let rectangle = new Shapes.Rectangle(10, 20);
console.log(rectangle.getArea()); // 200
在这个示例中,我们定义了一个名为Shapes
的命名空间,它包含一个名为Rectangle
的类。我们使用export
关键字将Rectangle
类暴露出去,以便其他地方可以访问它。注意,在使用命名空间时,我们需要使用namespace
关键字来定义命名空间,并使用.
运算符来访问其成员。
联合类型
联合类型表示一个值可以是多种类型之一。它使用|
符号将多个类型组合在一起。下面是一个简单的示例:
scss
typescriptCopy code
function display(value: string | number) {
console.log(value);
}
display("Hello"); // "Hello"
display(42); // 42
在这个示例中,我们定义了一个名为display
的函数,它接受一个参数,类型可以是string
或number
。我们可以传递一个string
或number
类型的值来调用该函数。
交叉类型
交叉类型表示一个值具有多个类型的属性。它使用&
符号将多个类型组合在一起。下面是一个简单的示例:
css
typescriptCopy code
interface A {
a: number;
}
interface B {
b: string;
}
type AB = A & B;
let ab: AB = {
a: 42,
b: "Hello"
};
console.log(ab); // { a: 42, b: "Hello" }
在这个示例中,我们定义了两个接口A
和B
,它们分别定义了a
和b
属性。我们使用交叉类型A & B
创建了一个新类型AB
,它具有a
和b
属性。我们创建了一个对象ab
,它具有a
和b
属性,并将其打印到控制台。
索引类型
索引类型允许我们使用字符串或数字类型的索引来访问对象的属性。它使用[]
符号表示。下面是一个简单的示例:
css
typescriptCopy code
interface Person {
name: string;
age: number;
[key: string]: any;
}
let person: Person = {
name: "Alice",
age: 30,
address: "123 Main St."
};
console.log(person.address); // "123 Main St."
在这个示例中,我们定义了一个名为Person
的接口,它具有name
和age
属性,并使用字符串类型的索引来允许其他任意属性。我们创建了一个名为person
的对象,并为其添加了一个address
属性。我们可以使用person.address
来访问该属性的值。
类型断言
类型断言是 TypeScript 中一种强制类型转换的机制,可以使我们的代码更加灵活、精确和安全。在 TypeScript 中,我们可以使用as
关键字来进行类型断言。
下面是一个简单的示例:
ini
typescriptCopy code
const value: unknown = "hello";
const message = (value as string).toUpperCase();
console.log(message); // "HELLO"
在这个示例中,我们定义了一个名为value
的变量,并给它赋值一个字符串。我们使用as
关键字将value
断言为string
类型,并调用toUpperCase
方法,并把它的结果赋值给message
变量。我们输出了message
的值。
可选链操作符
在 JavaScript 中,如果我们访问一个对象的属性,而这个属性不存在,就会返回 undefined
。例如:
arduino
javascriptCopy code
const person = { name: 'John', age: 30 }
console.log(person.job.title) // Uncaught TypeError: Cannot read property 'title' of undefined
在这个例子中,由于 person
对象中没有 job
属性,所以访问 person.job.title
时就会出现错误。
为了避免这种错误,TypeScript 3.7 引入了可选链操作符 ?.
。它可以让我们在访问一个对象的属性时,避免因为属性不存在而导致的错误。例如:
arduino
typescriptCopy code
const person = { name: 'John', age: 30 }
console.log(person.job?.title) // undefined
const job = { title: 'Software Engineer' }
person.job = job
console.log(person.job?.title) // Software Engineer
在第一次使用 person.job?.title
时,由于 person
对象中没有 job
属性,所以直接返回 undefined
,不会报错。
keyof 操作符
keyof
操作符用于获取一个类型的所有属性名称组成的联合类型,它可以使我们的代码更加灵活和可维护。在 TypeScript 中,我们可以使用keyof
操作符来定义泛型类型和访问对象属性。
下面是一个简单的示例:
typescript
typescriptCopy code
interface Person {
name: string;
age: number;
address: string;
}
type PersonKeys = keyof Person; // "name" | "age" | "address"
function getPropertyValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const person: Person = {
name: "Tom",
age: 18,
address: "Beijing",
};
console.log(getPropertyValue(person, "name")); // "Tom"
console.log(getPropertyValue(person, "age")); // 18
console.log(getPropertyValue(person, "address")); // "Beijing"
在这个示例中,我们定义了一个Person
接口,它包含了name
、age
和address
三个属性。我们使用keyof
操作符定义了一个名为PersonKeys
的类型,它是Person
接口的所有属性名称组成的联合类型。我们定义了一个名为getPropertyValue
的函数,它接受一个泛型类型参数T
和一个名为K
的属性名称参数。在函数内部,我们使用泛型类型参数和keyof
操作符来访问对象的属性,并返回属性的值。
装饰器
装饰器是一种特殊的声明,可以附加到类声明、方法、属性或参数上,用来描述类的行为。它们为我们提供了一种简洁明了的方式,以声明式的方式添加或修改类的行为。
装饰器在 TypeScript 中是一个实验性的功能,需要启用experimentalDecorators
编译选项。
下面是一个简单的示例:
typescript
typescriptCopy code
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${key} with args ${args}`);
const result = originalMethod.apply(this, args);
console.log(`Returned value: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@log
add(x: number, y: number) {
return x + y;
}
}
const calculator = new Calculator();
console.log(calculator.add(2, 3)); // "Calling add with args [2, 3]", "Returned value: 5", 5
在这个示例中,我们定义了一个名为log
的装饰器函数,它接受三个参数:目标类、方法名和方法描述符。我们使用装饰器修饰了Calculator
类中的add
方法,使其在调用时输出日志。我们创建了一个Calculator
实例,并调用了add
方法。
字面量类型
字面量类型是一种特殊的类型,它表示一个确定的值,可以用于增强代码的类型安全性。在 TypeScript 中,我们可以使用字面量类型来定义字符串、数字和布尔值等类型。
下面是一个简单的示例:
ini
typescriptCopy code
type Gender = "male" | "female";
interface Person {
name: string;
age: number;
gender: Gender;
}
const person: Person = { name: "Alice", age: 30, gender: "female" };
console.log(person); // { name: "Alice", age: 30, gender: "female" }
在这个示例中,我们定义了一个名为Gender
的字面量类型,它只允许取值"male"
或"female"
。我们定义了一个名为Person
的接口,它具有name
、age
和gender
三个属性,其中gender
的类型为Gender
。我们创建了一个Person
对象,并输出它的值。
类型推断
类型推断是 TypeScript 中一种自动推断类型的机制,可以使我们的代码更加简洁、易读和可维护。在 TypeScript 中,编译器会根据上下文自动推断表达式的类型,并把它们作为类型注解。
下面是一个简单的示例:
ini
typescriptCopy code
const message = "hello";
console.log(message.toUpperCase()); // "HELLO"
在这个示例中,我们定义了一个名为message
的常量,并给它赋值一个字符串。我们调用了toUpperCase
方法,并输出了它的结果。TypeScript 编译器会自动推断message
的类型为string
,并把它作为类型注解。
空值和未定义值
在 JavaScript 中,有两种特殊的值,分别是null
和undefined
。在 TypeScript 中,它们分别对应了null
和undefined
两种类型。
下面是一个简单的示例:
ini
typescriptCopy code
let x: null = null;
let y: undefined = undefined;
在这个示例中,我们定义了两个变量x
和y
,分别赋值为null
和undefined
。
never 类型
never
类型表示那些永远不会发生的值,它可以用于增强代码的类型安全性和可维护性。在 TypeScript 中,never
类型通常用于以下两种情况:
- 当一个函数抛出异常或无限循环时,它的返回类型就是
never
类型。 - 当一个函数返回类型是一个永远不会实现的类型,它的返回类型也是
never
类型。
下面是一个简单的示例:
typescript
typescriptCopy code
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
在这个示例中,我们定义了两个函数,一个是throwError
函数,它接受一个字符串参数message
,并抛出一个错误。另一个是infiniteLoop
函数,它会无限循环。这两个函数的返回类型都是never
类型。
条件类型
条件类型是一种根据一个条件来选择类型的方式,它可以在 TypeScript 中实现一些高级类型操作。在 TypeScript 中,我们可以使用extends
关键字和条件语句来定义条件类型。
下面是一个简单的示例:
typescript
typescriptCopy code
type IsString<T> = T extends string ? true : false;
type Result1 = IsString<string>; // true
type Result2 = IsString<number>; // false
在这个示例中,我们定义了一个名为IsString
的条件类型,它接受一个泛型类型参数T
。如果T
类型是字符串类型,则条件类型的结果为true
,否则为false
。我们还定义了两个变量Result1
和Result2
,它们分别是IsString<string>
和IsString<number>
类型的结果。