第 1 章:TS 环境搭建与基础配置¶
学习时间:2 小时 | 难度:⭐ 入门 | 前置知识:JavaScript 基础、Node.js 命令行
本章概览¶
本章将带你从零搭建一个生产级 TypeScript 开发环境,深入理解 tsconfig.json 每个重要选项的含义,并写出你的第一个有意义的 TypeScript 程序。
学习目标:
- 安装 Node.js 22 + pnpm,理解
tsx与tsc的区别 - 掌握
tsconfig.json的核心配置选项 - 理解 TypeScript 编译流程:
.ts→ 类型检查 →.js - 配置 VS Code 获得最佳开发体验
- 理解
strict模式的全部含义
1.1 安装与工具链¶
Node.js 22 + pnpm¶
Bash
# 推荐使用 fnm(快速 Node 版本管理器)安装 Node.js
# Windows
winget install Schniz.fnm
# macOS / Linux
curl -fsSL https://fnm.vercel.app/install | bash
# 安装并使用 Node.js 22 LTS(2025 年长期支持版)
fnm install 22
fnm use 22
node --version # v22.x.x
# 安装 pnpm(比 npm 快 2-3 倍,磁盘占用少 70%)
corepack enable pnpm
pnpm --version # 9.x.x
创建第一个 TypeScript 项目¶
Bash
mkdir learn-typescript && cd learn-typescript
pnpm init # 生成 package.json
# 安装核心工具链
pnpm add -D typescript # TypeScript 编译器(类型检查)
pnpm add -D @types/node # Node.js 类型定义
pnpm add -D tsx # 直接运行 .ts 文件(开发时用)
# 生成 tsconfig.json
npx tsc --init
tsx vs tsc:开发与生产的区别¶
| 工具 | 用途 | 速度 | 类型检查 |
|---|---|---|---|
tsx | 开发时直接运行 .ts | 极快(esbuild) | ❌ 不检查 |
tsc | 类型检查 + 编译 .js | 较慢 | ✅ 完整 |
tsc --noEmit | 仅类型检查,不生成文件 | 中等 | ✅ 完整 |
esbuild | 生产构建 | 极快 | ❌ 不检查 |
Bash
# 开发时:直接运行(快速迭代)
npx tsx src/index.ts
# CI/CD 中:类型检查(发现错误)
npx tsc --noEmit
# 生产构建:esbuild 打包
npx esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js
最佳实践:开发用
tsx,提交前用tsc --noEmit检查,部署用esbuild。
1.2 tsconfig.json 深度解析¶
这是一个适合现代项目的 tsconfig.json 完整配置:
JSON
{
"compilerOptions": {
// ── 目标与模块系统 ──────────────────────────────
"target": "ES2022", // 编译后 JS 的语法版本(支持顶层 await、class fields)
"lib": ["ES2022"], // 可用的内置类型库(DOM 项目加 "DOM")
"module": "NodeNext", // 模块系统(Node.js 22 用 NodeNext,浏览器用 ESNext)
"moduleResolution": "NodeNext", // 模块解析策略(必须与 module 匹配)
// ── 输出配置 ────────────────────────────────────
"outDir": "./dist", // 编译后文件输出目录
"rootDir": "./src", // 源码根目录
"declaration": true, // 生成 .d.ts 类型声明文件(库开发时必须)
"declarationMap": true, // 生成 .d.ts.map(支持 "Go to Definition" 跳转源码)
"sourceMap": true, // 生成 Source Map(调试时映射到原始 .ts 行号)
// ── 严格模式(强烈推荐全部开启)────────────────
"strict": true, // 开启所有严格检查(等于下面 5 项全部 true)
// "strictNullChecks": true, // (strict 已包含)null/undefined 不能赋值给其他类型
// "strictFunctionTypes": true, // (strict 已包含)函数参数逆变检查
// "noImplicitAny": true, // (strict 已包含)禁止隐式 any
// "strictPropertyInitialization": true, // (strict 已包含)类属性必须初始化
// "strictBindCallApply": true, // (strict 已包含)bind/call/apply 类型检查
// ── 额外质量检查 ────────────────────────────────
"noUnusedLocals": true, // 未使用的局部变量报错
"noUnusedParameters": true, // 未使用的函数参数报错
"noImplicitReturns": true, // 函数所有分支必须有 return
"noFallthroughCasesInSwitch": true, // switch-case 必须有 break/return
"noUncheckedIndexedAccess": true, // 数组/对象索引访问返回 T | undefined(更安全)
"exactOptionalPropertyTypes": true, // 可选属性不能显式赋值 undefined
// ── 路径与兼容性 ────────────────────────────────
"esModuleInterop": true, // 允许 import fs from 'fs'(而非 import * as fs)
"allowSyntheticDefaultImports": true, // 允许没有 default export 的模块用 default 导入
"resolveJsonModule": true, // 允许 import data from './data.json'
"skipLibCheck": true, // 跳过 node_modules 中 .d.ts 文件的类型检查(加快速度)
"forceConsistentCasingInFileNames": true, // 文件名大小写必须一致(跨平台兼容)
// ── 路径别名(可选,需配合 tsx/esbuild 插件)──
"baseUrl": ".",
"paths": {
"@/*": ["src/*"] // 使用 @/utils 代替 ../../utils
}
},
"include": ["src/**/*"], // 包含哪些文件参与编译
"exclude": [
"node_modules", // 永远排除
"dist", // 排除输出目录
"**/*.test.ts" // 测试文件单独用 vitest 的 tsconfig 处理
]
}
strict 模式的重要性¶
TypeScript
// ❌ 不开启 strictNullChecks 的危险代码
function getLength(str: string | null) {
return str.length; // 运行时 TypeError:Cannot read properties of null
}
// ✅ 开启 strictNullChecks 后,编译时就报错
function getLength(str: string | null) {
return str.length; // 错误:Object is possibly 'null'
// 必须处理 null:
}
function getLengthSafe(str: string | null): number {
if (str === null) return 0;
return str.length; // 这里 TypeScript 知道 str 一定是 string
}
// noUncheckedIndexedAccess 的威力
const arr = [1, 2, 3];
const item = arr[10]; // item 类型是 number | undefined(而非 number)
console.log(item.toFixed(2)); // 错误:item 可能 undefined,必须先判断
1.3 VS Code 最佳配置¶
必装扩展¶
- TypeScript Error Translator:把晦涩的 TS 错误翻译成人话
- Pretty TypeScript Errors:更美观的类型错误显示
- ESLint:代码质量检查(配合
typescript-eslint) - Prettier:代码格式化
.vscode/settings.json¶
JSON
{
// 使用项目本地的 TypeScript 版本(而非 VS Code 内置版本)
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
// 保存时自动修复 ESLint 错误
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
// 保存时用 Prettier 格式化
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
// TypeScript 语言服务优化
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.suggest.autoImports": true,
"typescript.inlayHints.parameterNames.enabled": "literals",
"typescript.inlayHints.variableTypes.enabled": true
}
1.4 基础类型速览¶
TypeScript
// ── 原始类型 ────────────────────────────────────────
let name: string = "Alice"; // 字符串
let age: number = 30; // 数字(整数与浮点数统一为 number)
let active: boolean = true; // 布尔值
let nothing: null = null; // null(strictNullChecks 下独立类型)
let missing: undefined = undefined; // undefined(同上)
let unique: symbol = Symbol("id"); // Symbol(唯一值)
let big: bigint = 9007199254740993n; // BigInt(超大整数)
// ── 字面量类型:值就是类型 ──────────────────────────
type Direction = "up" | "down" | "left" | "right"; // 只能是这四个值
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6; // 1~6 的整数
// ── 数组类型 ────────────────────────────────────────
const nums: number[] = [1, 2, 3]; // 语法 1
const strs: Array<string> = ["a", "b"]; // 语法 2(泛型写法,完全等价)
const pairs: [string, number][] = [["Alice", 30]]; // 元组数组
// ── 元组(Tuple):固定长度和类型的数组 ─────────────
const point: [number, number] = [10, 20]; // x, y 坐标
const entry: [string, number] = ["age", 30]; // 键值对
// ── 对象类型 ────────────────────────────────────────
const user: { name: string; age: number; email?: string } = {
name: "Alice",
age: 30,
// email 是可选的,可以不写
};
// ── any 与 unknown:都能接受任意值,但差别巨大 ──────
let dangerous: any = "hello"; // any 关闭类型检查,危险!
dangerous = 42; // 可以赋任意值
dangerous.toUpperCase(); // 不会报错,但运行时可能崩溃
let safe: unknown = "hello"; // unknown 是安全的 any
// safe.toUpperCase(); // 错误!必须先做类型检查
if (typeof safe === "string") {
safe.toUpperCase(); // OK:已经确认是 string
}
// ── never:永远不会到达的类型 ─────────────────────
function fail(msg: string): never {
throw new Error(msg); // 函数永远不会正常返回
}
function exhaustiveCheck(x: never): never {
throw new Error(`Unhandled case: ${x}`);
}
1.5 类型推断:让 TypeScript 帮你省力¶
TypeScript 非常聪明,很多时候不需要手动写类型注解:
TypeScript
// TypeScript 自动推断变量类型
const message = "Hello World"; // 推断为 string(常量更精确:推断为 "Hello World")
let count = 0; // 推断为 number
const arr = [1, 2, 3]; // 推断为 number[]
// 函数返回值推断
function add(a: number, b: number) {
return a + b; // 推断返回 number(不必写 : number)
}
// 解构赋值保持类型
const { name, age } = { name: "Alice", age: 30 };
// name: string, age: number — 自动推断
// 何时需要手动注解:
// 1. 函数参数(无法推断)
// 2. 声明变量但稍后赋值
// 3. 想要比推断更宽泛或更窄的类型
let later: string; // 声明先不赋值
later = "now";
// 4. 对象字面量需要明确约束
const config: { port: number; host: string } = {
port: 3000,
host: "localhost",
};
1.6 第一个完整程序:命令行 TODO 管理器¶
TypeScript
// src/todo.ts
// 综合运用本章所学:类型、接口、推断、null 检查
interface Todo {
id: number;
title: string;
done: boolean;
createdAt: Date;
}
// 模拟数据库(开启 noUncheckedIndexedAccess 后,访问结果是 Todo | undefined)
const todos: Todo[] = [];
let nextId = 1;
// 创建 Todo
function addTodo(title: string): Todo {
const todo: Todo = {
id: nextId++,
title,
done: false,
createdAt: new Date(),
};
todos.push(todo);
return todo;
}
// 标记完成
function completeTodo(id: number): boolean {
const todo = todos.find((t) => t.id === id); // Todo | undefined
if (todo === undefined) return false; // 处理 undefined
todo.done = true;
return true;
}
// 列出所有未完成项
function listPending(): Todo[] {
return todos.filter((t) => !t.done);
}
// 格式化输出(模板字面量类型)
function formatTodo(todo: Todo): string {
const status = todo.done ? "✅" : "⬜";
return `${status} [${todo.id}] ${todo.title}`;
}
// 使用
addTodo("学习 TypeScript 类型系统");
addTodo("完成第 1 章练习");
addTodo("搭建 Next.js 全栈项目");
completeTodo(1);
console.log("=== 待办事项 ===");
listPending().forEach((t) => console.log(formatTodo(t)));
// 运行:npx tsx src/todo.ts
🏋️ 本章练习¶
- 配置实验:关闭
tsconfig.json中的noUncheckedIndexedAccess,观察数组访问的类型变化 - 推断练习:写 5 个函数,不显式标注返回类型,让 TypeScript 推断,然后用鼠标悬停确认推断结果
- 严格模式体验:写一个接受
string | null参数的函数,在不处理 null 的情况下调用方法,观察编译错误
📌 本章小结¶
| 要点 | 说明 |
|---|---|
| 工具链 | tsx(开发)、tsc --noEmit(检查)、esbuild(构建) |
| tsconfig | strict: true 必开,noUncheckedIndexedAccess 强推 |
| 原始类型 | string/number/boolean/null/undefined/symbol/bigint |
| 特殊类型 | any(不安全)、unknown(安全)、never(不可达) |
| 类型推断 | 让 TS 自动推断,只在必要时手动注解 |
下一章:深入类型系统——接口、类型别名、枚举、联合类型与交叉类型。
TypeScript 5.8 · Node.js 22 · 2025