为什么选择 TypeScript

JavaScript 的痛点

JavaScript 作为动态类型语言,在大型项目开发中存在明显短板:

  • 作用域问题:ES5 及之前的var存在变量提升和函数级作用域隐患

  • 类型安全缺失:无类型检测机制,错误只能在运行时发现

  • 数组设计缺陷:早期 JS 数组内存空间不连续,影响性能

  • 示例:JS 类型隐患的 TS 模拟(无类型约束场景)

1
2
3
4
5
// 模拟 JS 无类型检测的隐患(TS 中关闭类型检查的等效场景)
function getStrLen(str: any) { // 用 any 模拟 JS 无类型约束
return str.length; // 若传入非字符串类型将报错
}
getStrLen(8); // 运行时错误:Cannot read properties of undefined (reading 'length')

TypeScript 的核心价值

TypeScript(简称 TS)是 JavaScript 的超集,官方定义为:

“TypeScript = JavaScript + 类型系统 + 最新 ECMAScript 特性”

核心优势:

  • 编译时类型检查,错误提前暴露(编码时→编译时→运行时)

  • 完全兼容 JS,所有.js 文件可直接作为.ts 文件使用

  • 开源维护,由微软持续迭代

  • 支持大型项目:Angular、Vue3、VSCode、Ant-Design 等均采用 TS 开发

环境搭建与运行

安装 TypeScript

1
2
3
4
5
// 终端命令(全局安装 TS 编译器)
npm install typescript -g

// 验证安装版本
tsc --version

运行 TS 代码的方式

编译为 JS 后运行

1
2
3
// 终端命令
tsc xxx.ts // 编译生成 xxx.js(TS → JS 转换)
node xxx.js // 运行编译后的 JS 文件

使用 ts-node 直接运行

1
2
3
4
5
6
// 终端命令(全局安装依赖)
npm install ts-node -g
npm install tslib @types/node -g

// 直接运行 TS 文件(无需手动编译)
ts-node xxx.ts

集成 Webpack(适用于前端项目)

通过 webpack 配置 ts-loader 或 babel-loader,实现 TS 自动编译打包,配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// webpack.config.ts 核心配置片段
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
}
};

核心语法与实操

基础类型定义

TS 支持 JavaScript 原有类型,并新增类型注解语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串类型
let str: string = 'vintor';
str = '123'; // 合法
// str = 123; // 编译错误:Type 'number' is not assignable to type 'string'

// 数字类型
let num: number = 18;

// 布尔类型
let isShow: boolean = true;

// 常量(只读,不可重新赋值)
const company: string = 'ctrip';
// company = '携程'; // 编译错误:Assignment to constant variable.

特殊类型:any

当变量类型无法确定时使用any,但实际开发中建议少用:

1
2
3
4
5
6
let name: any = "Vintor";
name = 18; // 合法,any 类型可任意赋值
name = { age: 18 }; // 合法

let arr: any[] = ['Vintor', 18, true];
// 注意:过度使用 any 会失去 TS 的类型检测优势,变成"AnyScript"

枚举类型(enum)

用于定义一组固定的取值集合,支持字符串和数字类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义 Coins 使用状态枚举
export enum COINS_STATUS {
/** 使用礼品卡或 Coins */
IN_USE = 'IN_USE',
/** 不使用礼品卡或 Coins */
NOT_USED = 'NOT_USED',
/** 禁止使用但依然展示 */
DISABLED = 'DISABLED',
/** 不展示 */
NOT_SHOW = 'NOT_SHOW'
}

// 使用枚举
const currentStatus: COINS_STATUS = COINS_STATUS.IN_USE;
console.log(currentStatus); // 输出:IN_USE

接口定义(interface/type)

用于描述对象的结构,两种定义方式的核心用法:

基础用法对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用 type 定义
type InfoType = {
name: string;
sex?: string; // 可选属性(? 表示可传可不传)
readonly age: number; // 只读属性(初始化后不可修改)
};

// 使用 interface 定义
interface InfoInterface {
name: string;
sex?: string;
readonly age: number;
}

// 实例化
const info1: InfoType = { name: 'Vintor', age: 18 };
const info2: InfoInterface = { name: 'Man', sex: 'male', age: 20 };

// info1.age = 25; // 编译错误:Cannot assign to 'age' because it is a read-only property

定义函数类型(type 专属)

1
2
3
4
5
6
7
8
9
// 使用 type 定义函数类型
type CalFunc = (num1: number, num2: number) => number;

// 实现函数
const add: CalFunc = (a, b) => a + b;
const multiply: CalFunc = (a, b) => a * b;

console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6

可选类型与空值处理

类似 Swift 的可选类型,防止空值导致的运行时错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义可选属性的接口
interface User {
name: string;
age?: number; // 可选属性,可能为 undefined
}

// 可选类型参数的函数
function getAge(info?: User): number {
// 使用 ?. 安全取值,避免 info 为 undefined 时报错
return info?.age || 0;
}

// 合法调用
console.log(getAge()); // 0
console.log(getAge({ name: 'Vintor' })); // 0
console.log(getAge({ name: 'Vintor', age: 18 })); // 18

泛型(Generic)

解决代码复用和类型安全问题,支持多种类型的通用逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 泛型函数:支持任意类型的参数传递
function getType, E>(arg1: T, arg2: E): [T, E] {
return [arg1, arg2];
}

// 自动推导类型
const result1 = getType('Vintor', 18); // [string, number]
// 显式指定类型
const result2 = getTypeVintor', true); // [string, boolean]

// 泛型接口
interface ResponseData {
code: number;
data: T;
message: string;
}

// 应用示例
const userResponse: ResponseData {
code: 200,
data: { name: 'Vintor', age: 18 },
message: 'success'
};

泛型命名规范(不成文约定):

  • T:Type(类型)

  • K/V:Key/Value(键值对)

  • E:Element(元素)

  • O:Object(对象)

元组(Tuple)

用于存储不同类型的固定长度数据集合,常用于函数返回值:

1
2
3
4
5
6
7
8
9
10
// 定义元组类型:[string, string, number]
const userInfo: [string, string, number] = ['Vintor', 'male', 18];

// 取值(按索引访问,类型固定)
const name = userInfo[0]; // string 类型
const gender = userInfo[1]; // string 类型
const age = userInfo[2]; // number 类型

// 元组 vs 数组:数组通常存储同一类型数据
const stringArr: string[] = ['a', 'b', 'c']; // 所有元素均为 string

联合类型(Union)

允许变量取值为多个类型中的任意一种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义联合类型别名
type CalType = number | string;

// 联合类型参数的函数
function calculate(num1: CalType, num2: CalType): number {
if (typeof num1 === 'number' && typeof num2 === 'number') {
return num1 + num2;
} else if (typeof num1 === 'string' && typeof num2 === 'string') {
return parseFloat(num1) + parseFloat(num2);
}
return 0;
}

// 合法调用
console.log(calculate(1, 3)); // 4
console.log(calculate('1', '3')); // 4

类型断言

用于明确指定变量的类型,类似类型转换:

1
2
3
4
5
6
7
8
9
10
11
12
// 方式 1:as 语法(推荐)
const anyValue: any = 'Vintor';
const strValue = anyValue as string;
console.log(strValue.toUpperCase()); // VINTOR

// 方式 2:尖括号语法(JSX 中不支持)
const numValue = 8;

// 强制解包(非空断言,不建议频繁使用)
const optionalValue: string | undefined = 'Hello';
const sureValue = optionalValue!; // 断言变量一定有值
console.log(sureValue.length); // 5

函数重载

解决联合类型在函数中的复杂判断问题,提供更精准的类型提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 函数声明(重载签名)
function calculate(num1: number, num2: number): number;
function calculate(num1: string, num2: string): number;

// 函数实现(实现签名)
function calculate(num1: number | string, num2: number | string): number {
if (typeof num1 === 'number' && typeof num2 === 'number') {
return num1 + num2;
} else if (typeof num1 === 'string' && typeof num2 === 'string') {
return parseFloat(num1) + parseFloat(num2);
}
return 0;
}

// 调用时将获得精准的类型提示
calculate(1, 3); // 正确
calculate('1', '3'); // 正确
// calculate(1, '3'); // 编译错误:无匹配的重载签名

实用工具类型

TS 内置多种实用工具类型,简化类型定义:

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
31
32
// 定义基础接口
interface User {
id: number;
name: string;
email: string;
age?: number;
}

// 1. Partial:所有属性变为可选
type PartialUser = Partial
// PartialUser = { id?: number; name?: string; email?: string; age?: number }

// 2. Readonly:所有属性变为只读
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: 'Vintor', email: 'test@ctrip.com' };
// user.id = 2; // 编译错误:只读属性不可修改

// 3. Pick:选择部分属性
type UserBasicInfo = Pick' | 'name'>;
// UserBasicInfo = { id: number; name: string }

// 4. Omit:排除部分属性
type UserWithoutId = Omit 'id'>;
// UserWithoutId = { name: string; email: string; age?: number }

// 5. Record:构建键值对类型
type PageInfo = Recordhome' | 'about' | 'contact', { title: string }>;
const pageConfig: PageInfo = {
home: { title: '首页' },
about: { title: '关于我们' },
contact: { title: '联系我们' }
};

项目配置:tsconfig.json

TS 的核心配置文件,控制编译行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"compilerOptions": {
"target": "ES2020", // 编译目标 ES 版本
"module": "ESNext", // 模块系统(ES 模块)
"strict": true, // 启用严格模式(强烈推荐)
"esModuleInterop": true, // 兼容 CommonJS 模块
"outDir": "./dist", // 编译输出目录
"rootDir": "./src", // 源码目录
"skipLibCheck": true, // 跳过库类型检查
"forceConsistentCasingInFileNames": true // 强制文件名大小写一致
},
"include": ["src/**/*"], // 需要编译的文件(src 下所有文件)
"exclude": ["node_modules", "dist"] // 排除的文件/目录
}

实战案例:TS 实现登录页面

需求分析

  • 账号输入框:最长 12 位,支持数字、下划线、大小写字母,不能以数字开头

  • 密码输入框:密文展示,最长 8 位

  • 登录按钮:点击后校验,不合法则爆红提示,合法则展示 “欢迎回来”

完整代码实现

image-20260202135851864

image-20260202135913203

image-20260202135941837

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// login.ts
// 登录表单 TypeScript 逻辑

document.addEventListener('DOMContentLoaded', () => {
// 获取 DOM 元素(类型断言为具体 HTML 元素类型)
const usernameInput = document.getElementById('username') as HTMLInputElement;
const passwordInput = document.getElementById('password') as HTMLInputElement;
const loginBtn = document.getElementById('loginBtn') as HTMLButtonElement;
const usernameError = document.getElementById('usernameError') as HTMLSpanElement;
const passwordError = document.getElementById('passwordError') as HTMLSpanElement;
const successMsg = document.getElementById('successMsg') as HTMLDivElement;

// 校验规则(定义类型约束)
const validateRules = {
username: (value: string): boolean => {
// 账号正则:1-12位,字母/下划线开头,可包含字母、数字、下划线
const reg = /^[a-zA-Z_][a-zA-Z0-9_]{0,11}$/;
return reg.test(value);
},
password: (value: string): boolean => {
// 密码正则:1-8位任意字符
const reg = /^.{1,8}$/;
return reg.test(value);
}
};

// 登录点击事件
loginBtn.addEventListener('click', () => {
const username = usernameInput.value.trim();
const password = passwordInput.value.trim();
let isInvalid = false;

// 重置状态
usernameInput.classList.remove('invalid');
passwordInput.classList.remove('invalid');
usernameError.textContent = '';
passwordError.textContent = '';
successMsg.textContent = '';

// 校验账号
if (!validateRules.username(username)) {
usernameInput.classList.add('invalid');
usernameError.textContent = '账号/密码有误!';
isInvalid = true;
}

// 校验密码
if (!validateRules.password(password)) {
passwordInput.classList.add('invalid');
passwordError.textContent = '账号/密码有误!';
isInvalid = true;
}

// 校验通过
if (!isInvalid) {
successMsg.textContent = '欢迎回来';
}
});
});
//HTML、CSS略

账号和密码并没有固定值,也没有和后端数据交互。只要账号和密码格式符合校验规则即可登录成功:

  • 账号要求:1-12位,字母或下划线开头,可包含字母、数字、下划线。例如:user_1、_abc、A123
  • 密码要求:1-8位任意字符。例如:123456、abc、!@#aBc

学习路线与资源推荐

学习路径(建议周期)

  • 基础阶段(1-2 周):类型系统、接口、类、基础语法

  • 进阶阶段(2-3 周):泛型、高级类型、装饰器、工具类型

  • 实战阶段(1-2 个月):React/Vue + TS 项目开发、Node.js + TS 后端开发

推荐资源

官方文档TypeScript 官网(最权威)

练习平台TypeScript Playground(在线编写运行 TS)

开源项目:Vue3 源码、Ant Design 源码(学习实战应用)

书籍:《Effective TypeScript》(进阶必备)

社区支持:Stack Overflow 的 TypeScript 标签,GitHub 的 DefinitelyTyped 项目(类型声明库),TypeScript 中文网(中文资源汇总)