前端开发基础(五)- typescript
TypeScript 简介
TypeScript 是 JavaScript 的超集,添加了静态类型定义和其他高级特性。它由微软开发和维护,旨在提高大型应用程序的开发效率和代码质量。TypeScript 代码最终会被编译成纯 JavaScript,因此可以在任何支持 JavaScript 的平台上运行。
TypeScript 基本结构
一个简单的 TypeScript 文件示例:
1 | // 定义类型 |
TypeScript 的编译过程
TypeScript 代码需要编译成 JavaScript 才能运行:
1 | # 安装 TypeScript |
推荐的 TypeScript 学习资源
课程推荐:
❗❗❗ 学习重点 🤔:
基本类型系统
原始类型:
number
:所有数值类型,包括整数和浮点数string
:文本字符串类型boolean
:真/假值(true/false)null
和undefined
:表示空值或未定义symbol
:唯一且不可变的值,常用作对象属性的键bigint
:表示任意精度的整数
复杂类型:
object
:表示所有非原始类型Array<T>
或T[]
:数组类型,如number[]
或Array<string>
tuple
:固定长度的数组,每个位置可以有不同类型,如[string, number]
enum
:枚举类型,为一组数值赋予有名字的常量
特殊类型:
any
:任意类型,跳过类型检查(应谨慎使用)unknown
:未知类型,比 any 更安全,需要类型检查后才能使用void
:通常用于表示函数没有返回值never
:表示永远不会发生的值的类型
类型注解与推断:
1
2
3
4
5// 类型注解
let name: string = "张三";
// 类型推断
let age = 25; // 自动推断为 number 类型
接口与类型别名
接口定义:
1
2
3
4
5interface Person {
name: string;
age: number;
sayHello(): void;
}可选与只读属性:
1
2
3
4
5interface Config {
readonly id: string; // 只读属性
name: string;
description?: string; // 可选属性
}类型别名:
1
2
3
4
5
6type Point = {
x: number;
y: number;
};
type ID = string | number; // 联合类型接口与类型别名的区别:
- 接口可以被扩展和实现,适用于描述对象形状
- 类型别名更适合复杂类型组合,如联合类型、交叉类型等
- 接口可以合并声明,类型别名不能
函数类型
函数类型定义:
1
2
3
4
5
6
7
8
9
10
11
12// 函数类型注解
function add(a: number, b: number): number {
return a + b;
}
// 函数类型表达式
let calculate: (x: number, y: number) => number;
// 接口定义函数类型
interface MathFunc {
(a: number, b: number): number;
}可选参数与默认值:
1
2
3
4
5
6
7function greet(name: string, greeting?: string): string {
return `${greeting || "你好"}, ${name}!`;
}
function multiply(a: number, b: number = 1): number {
return a * b;
}剩余参数:
1
2
3function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}函数重载:
1
2
3
4
5
6
7
8
9
10
11
12// 重载签名
function process(x: number): number;
function process(x: string): string;
// 实现
function process(x: number | string): number | string {
if (typeof x === "number") {
return x * 2;
} else {
return x.repeat(2);
}
}
类与面向对象编程
类的基本结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Person {
// 属性
name: string;
// 构造函数
constructor(name: string) {
this.name = name;
}
// 方法
greet(): string {
return `你好,我是 ${this.name}`;
}
}访问修饰符:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Employee {
public name: string; // 公共,默认
private salary: number; // 私有,仅在类内可访问
protected department: string; // 受保护,在类内和子类可访问
readonly id: number; // 只读属性
constructor(
name: string,
salary: number,
department: string,
id: number
) {
this.name = name;
this.salary = salary;
this.department = department;
this.id = id;
}
}继承与实现接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 继承
class Manager extends Employee {
reports: Employee[];
constructor(
name: string,
salary: number,
department: string,
id: number
) {
super(name, salary, department, id);
this.reports = [];
}
}
// 实现接口
interface Printable {
print(): void;
}
class Document implements Printable {
print() {
console.log("打印文档");
}
}静态成员与抽象类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 静态成员
class MathUtils {
static PI: number = 3.14159;
static calculateCircleArea(radius: number): number {
return this.PI * radius * radius;
}
}
// 抽象类
abstract class Shape {
abstract calculateArea(): number;
display(): void {
console.log(`面积是: ${this.calculateArea()}`);
}
}
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
高级类型
联合类型与交叉类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 联合类型
type ID = string | number;
function printID(id: ID) {
console.log(`ID: ${id}`);
}
// 交叉类型
type Employee = {
id: number;
name: string;
};
type Manager = {
department: string;
reports: Employee[];
};
type ManagerWithEmployeeInfo = Employee & Manager;类型断言:
1
2
3
4
5
6
7// 尖括号语法(不在JSX中使用)
let someValue: any = "这是一个字符串";
let strLength: number = (<string>someValue).length;
// as 语法
let someValue2: any = "这是另一个字符串";
let strLength2: number = (someValue2 as string).length;类型保护:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// typeof 类型保护
function process(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase(); // 这里 value 被视为 string
} else {
return value.toFixed(2); // 这里 value 被视为 number
}
}
// instanceof 类型保护
class Dog {
bark() {
console.log("汪汪!");
}
}
class Cat {
meow() {
console.log("喵喵!");
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // 这里 animal 被视为 Dog
} else {
animal.meow(); // 这里 animal 被视为 Cat
}
}字面量类型和可辨识联合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 字面量类型
type Direction = "north" | "south" | "east" | "west";
// 可辨识联合
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
}
}
泛型编程
泛型基础:
1
2
3
4
5
6
7
8// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用
let output1 = identity<string>("myString");
let output2 = identity(42); // 类型推断为 number泛型接口与类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 泛型接口
interface GenericBox<T> {
value: T;
getValue(): T;
}
// 泛型类
class Box<T> implements GenericBox<T> {
value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}泛型约束:
1
2
3
4
5
6
7
8
9
10
11
12
13// 使用接口约束泛型
interface HasLength {
length: number;
}
function getLength<T extends HasLength>(arg: T): number {
return arg.length;
}
// 使用
getLength("string"); // 可以,字符串有length属性
getLength([1, 2, 3]); // 可以,数组有length属性
// getLength(123); // 错误,数字没有length属性泛型工具类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 内置工具类型
interface Person {
name: string;
age: number;
}
// 将所有属性设为可选
type PartialPerson = Partial<Person>;
// 将所有属性设为必需
type RequiredPerson = Required<Person>;
// 将所有属性设为只读
type ReadonlyPerson = Readonly<Person>;
// 提取特定属性
type NameOnly = Pick<Person, "name">;
// 排除特定属性
type PersonWithoutAge = Omit<Person, "age">;
模块与命名空间
ES 模块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// math.ts
export function add(x: number, y: number): number {
return x + y;
}
export function subtract(x: number, y: number): number {
return x - y;
}
// app.ts
import { add, subtract } from "./math";
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2命名空间:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 命名空间(较旧的方式,现代项目推荐使用ES模块)
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
export class RegexValidator implements StringValidator {
private regex: RegExp;
constructor(regex: RegExp) {
this.regex = regex;
}
isValid(s: string): boolean {
return this.regex.test(s);
}
}
}
// 使用
let emailValidator = new Validation.RegexValidator(
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
);
console.log(emailValidator.isValid("test@example.com"));模块解析策略:
- Node.js/CommonJS 解析
- Classic 解析
- 路径映射配置 (tsconfig.json 中的 paths 和 baseUrl)
声明文件与类型定义
.d.ts
文件:1
2
3
4
5
6
7
8
9
10
11// 全局变量声明
declare const VERSION: string;
// 全局函数声明
declare function fetchData(url: string): Promise<any>;
// 全局命名空间
declare namespace MyLib {
function makeGreeting(name: string): string;
let version: number;
}第三方库类型声明:
1
2# 安装第三方库类型定义
npm install --save-dev @types/lodash模块扩展:
1
2
3
4
5
6
7
8
9// 为现有模块添加新功能
declare module "express" {
interface Request {
user?: {
id: string;
name: string;
};
}
}编写声明文件的最佳实践:
- 保持类型定义精确
- 提供尽可能多的类型信息
- 使用
readonly
表示不可变属性 - 考虑类型库的可维护性
TypeScript 编译配置
tsconfig.json 文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14{
"compilerOptions": {
"target": "es6", // 编译目标 ES 版本
"module": "commonjs", // 模块系统
"strict": true, // 启用所有严格类型检查选项
"esModuleInterop": true, // 启用 ES 模块互操作性
"skipLibCheck": true, // 跳过声明文件的类型检查
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
"outDir": "./dist", // 输出目录
"rootDir": "./src" // 源码目录
},
"include": ["src/**/*"], // 包含的文件
"exclude": ["node_modules"] // 排除的文件
}编译选项详解:
noImplicitAny
:不允许隐式的 any 类型strictNullChecks
:启用严格的 null 检查noImplicitThis
:不允许 this 的类型为 anyalwaysStrict
:以严格模式解析并为每个源文件生成 “use strict”sourceMap
:生成相应的 .map 文件declaration
:生成相应的 .d.ts 文件
项目引用:
1
2
3{
"references": [{ "path": "../common" }]
}构建工具集成:
- webpack 与 ts-loader
- Babel 与 @babel/preset-typescript
- esbuild 等现代构建工具
TypeScript 与框架集成
React 与 TypeScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 函数组件
interface GreetingProps {
name: string;
count?: number;
}
const Greeting: React.FC<GreetingProps> = ({ name, count = 1 }) => {
return (
<h1>
Hello, {name}! You've visited {count} times.
</h1>
);
};
// 使用 hooks
const [state, setState] = React.useState<string>("");Vue 与 TypeScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import { defineComponent, ref } from "vue";
export default defineComponent({
props: {
message: {
type: String,
required: true,
},
},
setup(props) {
const count = ref(0);
function increment() {
count.value++;
}
return {
count,
increment,
};
},
});Svelte 与 TypeScript:
1
2
3
4
5
6
7
8
9
10
11<script lang="ts">
export let message: string;
let count: number = 0;
function increment(): void {
count += 1;
}
</script>
<h1>{message}</h1>
<button on:click={increment}>Clicked {count} times</button>Node.js 与 TypeScript:
1
2
3
4
5
6
7
8
9
10
11
12import * as express from "express";
const app = express();
const port = 3000;
app.get("/", (req: express.Request, res: express.Response) => {
res.send("Hello from TypeScript!");
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
高级 TypeScript 技巧
条件类型:
1
2
3
4type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false映射类型:
1
2
3
4
5
6
7
8
9
10
11
12type Properties = "name" | "age" | "address";
type User = {
[P in Properties]: string;
};
// 相当于
// type User = {
// name: string;
// age: string;
// address: string;
// }递归类型:
1
2
3
4// 递归类型定义嵌套数组
type NestedArray<T> = Array<T | NestedArray<T>>;
const nested: NestedArray<number> = [1, 2, [3, 4, [5, 6]]];模板字面量类型:
1
2
3type EventName<T extends string> = `${T}Changed`;
type Events = EventName<"name" | "title">; // 'nameChanged' | 'titleChanged'infer 关键字:
1
2
3
4
5
6
7type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturn = ReturnType<typeof add>; // number
TypeScript 与设计模式
单例模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Singleton {
private static instance: Singleton;
private constructor() {}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
public someMethod(): void {
console.log("方法已被调用");
}
}
// 使用
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true工厂模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25interface Product {
operation(): string;
}
class ConcreteProduct1 implements Product {
operation(): string {
return "ConcreteProduct1";
}
}
class ConcreteProduct2 implements Product {
operation(): string {
return "ConcreteProduct2";
}
}
class Factory {
createProduct(type: string): Product {
if (type === "1") {
return new ConcreteProduct1();
} else {
return new ConcreteProduct2();
}
}
}装饰器模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 需要在 tsconfig.json 中启用 experimentalDecorators
function log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with:`, args);
const result = originalMethod.apply(this, args);
console.log(`Result:`, result);
return result;
};
return descriptor;
}
class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(1, 2);
// 输出:
// Calling add with: [1, 2]
// Result: 3
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 AnA.!
评论