TypeScript简介
一、简介
TypeScript是JavaScript的一个超集,在标准JavaScript的基础上添加了静态类型系统。它有
1、特性
- 静态类型检查
在编写代码时可以为变量、函数参数、返回值等显式地声明数据类型(如string、number、boolean以及自定义接口interface或类class等)。
使用静态类型检查可以在编译阶段就能检查出类型不匹配的错误,减少运行时错误,提高代码的健壮性,增强代码可读性。
- 面向对象编程增强
TypeScript原生支持并扩展了ES6的类(class)、接口(interface)、继承(extends)、访问修饰符(public、private、protected)等面向对象概念。
2、工作原理
-
使用
npm install -g typescript安装TypeScript -
编写TypeScript文件,以
.ts命名 -
使用TypeScript编译器(tsc)将
.ts文件转换成.js文件 -
运行编译生成后的
*.js文件
3、Hello World
hello.ts
const message:string = "Hello World!";
console.log(message);
- 编译
tsc hello.ts
生成hello.js:
var message = "Hello World!";
console.log(message);
- 运行
运行编译生成后的hello.js:
node hello.js
输出:
Hello World!
4、配置
tsconfig.json是每个TypeScript项目的核心文件,它告诉TypeScript编译器如何处理你的代码,包含哪些文件,以及启用或禁用哪些功能等。可以通过tsc --init生成tsconfig.json文件。
主要的配置项如下:
- compilerOptions
控制TypeScript如何编译代码。
- include
配置用于编译的文件或文件夹。
- exclude
配置需要排除的文件或文件夹。
- files
明确列出需要包含的文件。
- extends
继承另一个配置文件中的配置。
例如:
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"strict": true,
"baseUrl": ".",
"paths": {
"@app/*": ["src/app/*"]
},
"outDir": "dist",
"esModuleInterop": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
二、语法
1、简单类型
- Boolean
let isActive: boolean = true;
let hasPermission = false;
- Number
表示整数和浮点数。
let decimal: number = 6;
//16进制
let hex: number = 0xf00d;
//2进制
let binary: number = 0b1010;
//8进制
let octal: number = 0o744;
let float: number = 3.14;
- String
let color: string = "blue";
let fullName: string = 'John Doe';
let age: number = 30;
let sentence: string = `Hello, my name is ${fullName} and I'll be ${age + 1} next year.`;
console.log(sentence);
输出:
Hello, my name is John Doe and I'll be 31 next year.
- BigInt
使用n作为后缀,表示大于2的53次方减1的整数。
const bigNumber: bigint = 9007199254740991n;
const hugeNumber = BigInt(9007199254740991);
- Symbol
通常用于创建唯一的属性键和常量。
const uniqueKey: symbol = Symbol('description');
const obj = {
[uniqueKey]: 'This is a unique property'
};
console.log(obj[uniqueKey]);
使用tsc hello.ts --target es2016编译后运行,输出:
This is a unique property
2、特殊类型
- any
any类型是TypeScript中最灵活的类型,本质上是告诉编译器跳过某个特定变量的类型检查。但应谨慎使用,因为它绕过了TypeScript的类型安全特性。
let u = true;
u = "string";
Math.round(u);
编译时报错:
error TS2322: Type 'string' is not assignable to type 'boolean'.
u = "string";
error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'number'.
Math.round(u);
将变量设置为any类型会禁用类型检查:
let v: any = true;
v = "string";
Math.round(v);
可以成功编译。
- unknown
unknown是类型安全的any,意思是“这可能是任何东西,所以你必须在使用前进行某种检查”。
任何值都可以赋给unknown:
let userInput: unknown;
userInput = 42;
userInput = true;
userInput = [1, 2, 3];
userInput = "Hello, World!";
使用unknown时必须先进行类型检查,如果直接使用:
console.log(userInput.length);
编译时会提示:
error TS2339: Property 'length' does not exist on type 'unknown'
需要增加类型检查:
if (typeof userInput === "string") {
console.log(userInput.length);
}
- never
never表示那些永远不会出现的值类型,例如:下面的函数总是抛出错误永不返回:
function throwError(message: string): never {
throw new Error(message);
}
3、类型推断
TypeScript可以根据变量的初始值自动确定(推断)其类型:
let username = "alice";
let score = 100;
//推断类型为布尔数组
let flags = [true, false, true];
//推断返回值类型为数值
function add(a: number, b: number) {
return a + b;
}
- 对象字面量推断
const user = {
name: "Alice",
age: 30,
isAdmin: true
};
//推断属性是否存在
console.log(user.name);
console.log(user.email);//Error
编译时会出现错误:
error TS2339: Property 'email' does not exist on type '{ name: string; age: number; isAdmin: boolean; }'.
- 无法推断类型
// Type is 'any'
let something;
something = 'hello';
something = 42;
无法确定正确的类型时,TypeScript会退回到any类型,从而禁用类型检查。
4、显式类型
使用显式类型可以明确声明变量的类型,通常用于:函数参数与返回值类型、对象字面量以及当初始值可能不是最终类型时。
- 基本类型
// String
const greeting: string = "Hello, TypeScript!";
// Number
const userCount: number = 42;
// Boolean
const isLoading: boolean = true;
// Array of numbers
const scores: number[] = [100, 95, 98];
- 类型不匹配
显式类型不匹配:
let username: string = "alice";
username = 42;//Error: Type 'number' is not assignable to type 'string'
隐式类型不匹配:
let score = 100;
score = "high";//Error: Type 'string' is not assignable to type 'number'
- 函数
function greet(name: string): string {
return `Hello, ${name}!`;
}
//调用函数时会确保参数类型正确
greet("Alice");
greet(42); //Error
编译时会出现错误:
error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
5、数组
const names: string[] = [];
names.push("Dylan");
//类型检查,编译会报错
names.push(3);
- readonly
使用readonly可以阻止数组被修改:
const names: readonly string[] = ["Dylan"];
names.push("Jack");
编译时报错:
error TS2339: Property 'push' does not exist on type 'readonly string[]'.
6、元组
元组是一个带有预定义长度和每个索引类型的数组,它允许数组中的每个元素都是已知的值类型。
let ourTuple: [number, boolean, string];
ourTuple = [5, false, 'Coding God was here'];
- readonly
也可以通过readonly设置为只读:
const ourReadonlyTuple: readonly [number, boolean, string] = [5, true, 'The Real Coding God'];
ourReadonlyTuple.push('Coding God took a day off');
编译时会出错:
error TS2339: Property 'push' does not exist on type 'readonly [number, boolean, string]'.
- 命名元组与结构化
可以为每个索引命名:
const graph: [x: number, y: number] = [55.2, 41.3];
const [x,y] = graph;
console.log(x);//55.2
console.log(y);//41.3
console.log(graph[0]);//55.2
7、对象类型
- 示例
const car: { type: string, model: string, year: number } = {
type: "Toyota",
model: "Corolla",
year: 2009
};
- 类型推断
const car = {
type: "Toyota",
};
car.type = "Ford";
car.type = 2;//Error
编译时报错:
error TS2322: Type 'number' is not assignable to type 'string'.
- 可选属性
下面例子中mileage为可选属性:
const car: { type: string, mileage?: number } = {
type: "Toyota"
};
car.mileage = 2000;
如果不设置可选,则在编译时会报错:
const car: { type: string, mileage: number } = {
type: "Toyota",
};
car.mileage = 2000;
hello.ts:1:7 - error TS2741: Property 'mileage' is missing in type '{ type: string; }' but required in type '{ type: string; mileage: number; }'.
- 索引签名
索引签名可用于没有定义属性列表的对象:
const nameAgeMap: { [index: string]: number } = {};
nameAgeMap.Jack = 25;
nameAgeMap.Mark = "Fifty";//Error
指定了索引类型和值类型,上面示例在编译时报错:
error TS2322: Type 'string' is not assignable to type 'number'.
8、枚举
枚举有两种类型:字符串(string)和数值(numeric)。
默认情况下,枚举会将第一个值初始化为0,并在每个新增值上加1:
enum CardinalDirections {
North,
East,
South,
West
}
console.log(CardinalDirections.North);
console.log(CardinalDirections.South);
输出:
0
2
- 初始化
以设置第一个数值枚举的值,后面的值依次递增:
enum CardinalDirections {
North = 10,
East,
South,
West
}
console.log(CardinalDirections.North);
console.log(CardinalDirections.South);
输出:
10
12
- 完全初始化
可以为每个枚举值分配独特的数字值:
enum StatusCodes {
NotFound = 404,
Success = 200,
Accepted = 202,
BadRequest = 400
}
console.log(StatusCodes.NotFound);
console.log(StatusCodes.Success);
输出:
404
200
- 字符串枚举
enum CardinalDirections {
North = 'N',
East = "E",
South = "S",
West = "W"
};
console.log(CardinalDirections.North);
console.log(CardinalDirections.West);
输出:
N
W
9、类型别名
允许用自定义名称(别名)定义类型:
//定义类型别名
type Year = number
type CarType = string
type CarModel = string
type Car = {
year: Year,
type: CarType,
model: CarModel
}
//使用别名定义变量类型
const carYear: Year = 2001
const carType: CarType = "Toyota"
const carModel: CarModel = "Corolla"
const car: Car = {
year: carYear,
type: carType,
model: carModel
};
10、接口
interface Rectangle {
height: number,
width: number
}
const rectangle: Rectangle = {
height: 20,
width: 10
};
- 接口合并
interface Animal {
name: string;
}
interface Animal {
age: number;
}
const dog: Animal = {
name: "Fido",
age: 5
};
console.log(dog.name);
console.log(dog.age);
输出:
Fido
5
- 接口继承
interface Rectangle {
height: number,
width: number
}
interface ColoredRectangle extends Rectangle {
color: string
}
const cr: ColoredRectangle = {
height: 20,
width: 10,
color: "red"
};
console.log(cr.height);
console.log(cr.width);
console.log(cr.color);
输出:
20
10
red
11、类
TypeScript为JavaScript类添加了类型和可见性修饰符。
- 成员类型
类的成员(属性和方法)通过类型注释进行类型化,类似于变量:
class Person {
name: string;
}
const person = new Person();
person.name = "Jane";
- 可见性
class Person {
private name: string;
public constructor(name: string) {
this.name = name;
}
public getName(): string {
return this.name;
}
}
const person = new Person("Jane");
console.log(person.getName()); //Jane
- 参数属性
TypeScript在参数中添加可见性修饰符:
class Person {
//name为私有变量
public constructor(private name: string) {}
public getName(): string {
return this.name;
}
}
const person = new Person("Jane");
console.log(person.getName());//Jane
- 只读
使用readonly可以防止类成员被修改:
class Person {
private readonly name: string;
public constructor(name: string) {
//name属性赋值后不允许被修改
this.name = name;
}
public getName(): string {
return this.name;
}
}
- 实现接口
interface Shape {
getArea(): number;
}
class Rectangle implements Shape {
public constructor(protected readonly width: number, protected readonly height: number) {
}
public getArea() : number {
return this.width * this.height;
}
}
- 继承类
class Square extends Rectangle {
public constructor(width: number) {
super(width, width);
}
}
- 覆盖
继承时可以使用相同名称的成员替换父类的成员:
class Square extends Rectangle {
public constructor(width: number) {
super(width, width);
}
public override getArea(): number {
return this.width * this.width;
}
}
- 抽象类
可以使用abstract将类定义为抽象类,类中未实现的成员也使用abstract关键词修饰;抽象类无法直接实例化。
abstract class Polygon {
public abstract getArea(): number;
public toString(): string {
return `Polygon[area=${this.getArea()}]`;
}
}
class Rectangle extends Polygon {
public constructor(protected readonly width: number, protected readonly height: number) {
super();
}
public getArea(): number {
return this.width * this.height;
}
}
12、或类型
当一个值是多个类型时,可以使用联合(或)类型|。
例如,下面的例子中参数是string或number类型:
function printStatusCode(code: string | number) {
console.log(`My status code is ${code}.`)
}
printStatusCode(404);
printStatusCode('404');
输出:
My status code is 404.
My status code is 404.
- 类型错误
使用|类型时,需要避免类型错误:
function printStatusCode(code: string | number) {
console.log(`My status code is ${code.toUpperCase()}.`);
}
编译时报错:
error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
13、函数
- 返回值类型
可以明确定义函数的返回值类型:
function getTime(): number {
return new Date().getTime();
}
- 返回空类型
function printHello(): void {
console.log('Hello!');
}
- 参数
函数参数的类型与变量声明的语法类似:
function multiply(a: number, b: number) {
return a * b;
}
- 可选参数
function add(a: number, b: number, c?: number) {
return a + b + (c || 0);
}
- 默认参数
function pow(value: number, exponent: number = 10) {
return value ** exponent;
}
- 命名参数
function divide({ dividend, divisor }: { dividend: number, divisor: number }) {
return dividend / divisor;
}
- 剩余参数
剩余参数可以将一个不定数量的参数表示为一个数组,它必须是函数参数列表中的最后一个参数。
function add(a: number, b: number, ...rest: number[]) {
return a + b + rest.reduce((p, c) => p + c, 0);
}
14、类型断言
有时处理类型时需要覆盖变量的类型,可以使用as关键字改变变量的类型:
let x: unknown = 'hello';
console.log((x as string).length);//5
但并不会改变变量值数据的类型:
let x: unknown = 4;
console.log((x as string).length);
由于值是数值类型,因此输出undefined。
也可以用<>,和as效果一样:
let x: unknown = 'hello';
console.log((<string>x).length);
为了避免TypeScript在覆盖类型时可能抛出的类型错误,可以先将类型改为unknown,然后再改为目标类型:
let x = 5;
console.log(((x as unknown) as string).length);
输出:undefined
15、泛型
- 函数
带有泛型的函数可以创建更通用的函数:
function createPair<S, T>(v1: S, v2: T): [S, T] {
return [v1, v2];
}
console.log(createPair<string, number>('hello', 42));
输出:
[ 'hello', 42 ]
- 类
泛型可以被赋予默认值,如果没有指定或推断其他值,则使用默认值:
class NamedValue<T = string> {
private _value: T | undefined;
constructor(private name: string) {}
public setValue(value: T) {
this._value = value;
}
public getValue(): T | undefined {
return this._value;
}
public toString(): string {
return `${this.name}: ${this._value}`;
}
}
let value = new NamedValue('myKey');
value.setValue('myValue');
console.log(value.toString());
输出:
myKey: myValue
- 继承
<S extends Shape | Animal>
16、keyof
- 提取键类型
keyof可以从对象类型中提取键类型:
interface Person {
name: string;
age: number;
}
function printPersonProperty(person: Person, property: keyof Person) {
console.log(`Printing person property ${property}: "${person[property]}"`);
}
let person = {
name: "Max",
age: 27
};
printPersonProperty(person, "name");
输出:
Printing person property name: "Max"
- 提取索引类型
keyof也可以与索引签名一起使用提取索引类型:
type StringMap = { [key: string]: unknown };
function createStringPair(property: keyof StringMap, value: string): StringMap {
return { [property]: value };
}
console.log(createStringPair('myKey', 'myValue'));
输出:
{ myKey: 'myValue' }
17、类型/属性检查
- typeof
typeof可以在运行时检查原始值的类型。
function formatValue(value: string | number): string {
if (typeof value === 'string') {
return value.trim().toUpperCase();
} else {
return value.toFixed(2);
}
}
console.log(formatValue(' hello '));
console.log(formatValue(56.1234));
输出:
HELLO
56.12
- instanceof
instanceof可以检查对象是否是特定类或构造函数的实例。
class Bird {
fly() {
console.log("Flying...");
}
}
class Fish {
swim() {
console.log("Swimming...");
}
}
function move(animal: Bird | Fish) {
if (animal instanceof Bird) {
animal.fly();
} else {
animal.swim();
}
}
move(new Bird());
move(new Fish());
输出:
Flying...
Swimming...
- in
in可以检查对象上是否存在属性。
interface Dog {
bark(): void;
}
interface Cat {
meow(): void;
}
function makeSound(animal: Dog | Cat) {
if ("bark" in animal) {
animal.bark();
} else {
animal.meow();
}
}
const myDog: Dog = {
bark: () => {
console.log("Woof! Woof!");
}
};
const myCat: Cat = {
meow: () => {
console.log("Meow! Meow!");
}
};
makeSound(myDog);
makeSound(myCat);
输出:
Woof! Woof!
Meow! Meow!
18、工具类型
- Partial
Partial将对象中的所有属性改为可选。
interface Point {
x: number;
y: number;
}
//x和y可选
let pointPart: Partial<Point> = {};
pointPart.x = 10;
- Required
Required可以将对象中的所有属性改为必须:
interface Car {
make: string;
model: string;
mileage?: number;
}
let myCar: Required<Car> = {
make: 'Ford',
model: 'Focus',
mileage: 12000 // `Required`
};
- Record
可以通过Record定义具有特定键类型和值类型的对象:
const nameAgeMap: Record<string, number> = {
'Alice': 21,
'Bob': 25
};
- Omit
Omit可以移除对象类型的键。
interface Person {
name: string;
age: number;
location?: string;
}
const bob: Omit<Person, 'age' | 'location'> = {
name: 'Bob'
};
上面bob只能定义name,其他两个属性已被移除。
- Pick
Pick可以移除对象类型中除指定键外的其他键。
interface Person {
name: string;
age: number;
location?: string;
}
const bob: Pick<Person, 'name'> = {
name: 'Bob'
};
上面通过Pick只保留了name属性。
- Exclude
Exclude从并集中移除类型。
type Primitive = string | number | boolean
const value: Exclude<Primitive, string> = true;
const num : Exclude<Primitive, string> = 123;
上面例子中Exclude<Primitive, string>表示在Primitive类型中移除string,因此只能定义为boolean或number类型。
- ReturnType
ReturnType可以提取函数的返回类型。
type PointGenerator = () => { x: number; y: number; };
const point: ReturnType<PointGenerator> = {
x: 10,
y: 20
};
- Parameters
提取函数的参数类型,值为数组形式。
type PointPrinter = (p: { x: number; y: number; }) => void;
const point: Parameters<PointPrinter>[0] = {
x: 10,
y: 20
};
- Readonly
Readonly可以创建一个新类型,其中所有属性都是只读的,一旦赋值就无法修改。
interface Person {
name: string;
age: number;
}
const person: Readonly<Person> = {
name: "Dylan",
age: 35,
};
person.name = 'Israel';
会出现编译错误:
error TS2540: Cannot assign to 'name' because it is a read-only property.