Skip to content

类型系统基础(Type System Basics)

核心问题解析

问题一:为什么写代码时能提前发现错误?

在纯 JavaScript 中,很多错误要到运行时才会暴露:

js
function add(a, b) {
  return a + b;
}
add("1", "2"); // "12" —— 是你想要的吗?

TypeScript编写阶段 就能提示风险:

ts
function add(a: number, b: number): number {
  return a + b;
}
add("1", "2"); // ❌ 编译报错:类型 'string' 不能赋给 'number'

答案:TypeScript 是一门静态类型语言,它在代码编译阶段进行类型检查,无需执行即可发现类型不匹配的问题。

问题二:any 到底能不能用?

初学者常这样“绕过”类型检查:

ts
let value: any = "hello";
value = 123;
value.push(4); // ❌ 运行时报错,但 TS 不报!

虽然 any 让你“自由”,但也意味着:

  • 放弃了类型检查
  • 失去了编辑器智能提示
  • 增加了维护成本

建议使用场景:

  • 迁移旧 JS 项目时临时使用
  • 确实无法确定类型的动态值(应尽快收敛)

避免滥用:any 是类型安全的“后门”,开多了等于没装防盗系统。

问题三:变量还没赋值,怎么就有类型了?

看这个例子:

ts
let age: number;
age = "30"; // ❌ 报错!虽然还没赋值,但类型已定

即使 age 尚未被赋值,TS 也根据类型注解知道它将来只能是 number

再看类型推导:

ts
let name = "Alice"; // TS 自动推导为 string
name = 123;         // ❌ 报错

结论:TS 要么通过类型注解明确指定,要么通过初始值自动推导,确保每个变量都有确定的类型。

学习目标详解

目标一:理解静态类型检查机制

  • 静态类型:类型在编译时确定,而非运行时。
  • 检查时机:你在 VSCode 里看到的红色波浪线,就是 TSC(TypeScript Compiler)在实时检查。
  • 不改变运行逻辑:TS 编译后会擦除类型,生成纯 JS,所以最终运行的还是 JS 引擎。

目标二:掌握原始类型与字面量类型

类型说明示例
string字符串"hello"
number数字(无 int/float 区分)42, 3.14
boolean布尔值true, false
null空值null
undefined未定义undefined
void无返回值function log(): void { }
never永不返回function error(): never { throw new Error() }

字面量类型:更精确的类型,限定具体值。

ts
let direction: "left" | "right" | "up" | "down" = "left";
direction = "forward"; // ❌ 报错

字面量类型 + 联合类型 = 枚举替代方案

目标三:区分类型注解与类型推导

方式语法场景
类型注解显式声明参数、返回值、未初始化变量
类型推导自动判断有初始值的变量、常量
ts
// 显式注解
let count: number;
let greet: (name: string) => void;

// 类型推导
const PI = 3.14;        // 推导为 number
const isActive = true;  // 推导为 boolean
const list = [1,2,3];   // 推导为 number[]

规则:一旦推导完成,就不能违背。

ts
const user = { name: "Bob" };
user.age = 30; // ❌ 报错:Property 'age' does not exist

解决:使用接口或类型扩展。

目标四:理解 unknown 与 any 的区别

类型可读可写可调方法安全性
any❌低
unknown✅(需校验)✅(需校验)✅ 高
ts
let anything: any = "test";
anything.call();     // ✅ 允许(可能运行时报错)
anything.toFixed();  // ✅ 允许

let unknownVal: unknown = "test";
unknownVal();        // ❌ 报错
unknownVal.toFixed(); // ❌ 报错

// 必须先做类型收窄
if (typeof unknownVal === "number") {
  unknownVal.toFixed(); // ✅ 此时 OK
}

最佳实践:

  • unknown 替代 any 接收不可信输入(如 API 返回值)
  • 使用类型守卫进行安全转换

总结:本节核心要点

概念关键点
静态检查错误前置,提升开发体验
类型注解 vs 推导明确优先,推导辅助
any尽量不用,用则速改
unknown安全接收任意值的首选
字面量类型提升类型精度,减少非法值

练习题

练习题 1:类型注解纠错

题目:以下代码存在类型错误,请指出错误原因并修正。

ts
let age: number;
age = "25";

问题:

  • 哪里出错了?为什么?
  • 如何修正才能让代码通过编译?

练习题 2:使用字面量类型限制取值

题目:定义一个变量 method,它只能是 "GET"、"POST"、"PUT" 或 "DELETE" 中的一个。

ts
let method: ____________ = "GET";

要求:

  • 补全类型注解。
  • 写一行代码验证类型安全(例如尝试赋值 "PATCH",应报错)。

练习题 3:分析类型推导结果

题目:观察以下变量声明,写出 TypeScript 推导出的类型,并说明是否可以添加新属性。

ts
const user = {
  name: "Alice",
  age: 30
};

user.email = "alice@example.com"; // 是否允许?为什么?

问题:

  • user 的推导类型是什么?
  • 能否添加 email 属性?如果不能,如何修改声明使其支持?

练习题 4:any vs unknown 安全性对比

题目:以下两段代码,哪一段更安全?为什么?并改写不安全的代码,使其类型安全。

ts
// 片段 A
function logLengthA(input: any) {
  console.log(input.length);
}

// 片段 B
function logLengthB(input: unknown) {
  console.log(input.length); // 报错?
}

要求:

  • 释 A 和 B 的行为差异。
  • 修改 logLengthB,使其在安全的前提下访问 length 属性。

综合应用:函数参数类型设计

题目:实现一个函数 greet,满足以下要求:

  • 接收一个参数 person,该参数必须包含 name(字符串)和 age(可选)。
  • 返回值类型为字符串,格式如 "Hello, I'm Alice, 25 years old."。
  • 如果没有 age,则不显示年龄部分。
ts
function greet(person: ____________) {
  // 请补全类型并实现函数
}

要求:

  • 使用接口或类型别名定义 person 的结构。
  • 利用可选属性处理 age。
  • 验证以下调用是否合法:
ts
greet({ name: "Bob" });
greet({ name: "Charlie", age: 30 });
greet({ name: "David", email: "d@d.com" }); // 应该报错吗?