Skip to content

第2章 类型系统入门

本章定位:理解TypeScript静态类型系统的核心概念。对比Python的type hint,掌握如何在TypeScript中声明和使用类型,以及类型推断的工作原理。


静态类型:变量类型在编译时确定,编译时进行类型检查。代表语言:C++、Java、TypeScript。

动态类型:变量类型在运行时确定,类型可以随时改变。代表语言:Python、JavaScript、Ruby。

TypeScript采用”可选静态类型”的设计哲学:

  • 可以显式声明类型
  • 可以让TypeScript自动推断类型
  • 完全兼容JavaScript的动态行为

这意味着你可以逐步将JavaScript代码迁移到TypeScript,而不是要求一开始就完整标注。

Python是动态类型语言,type hint只是给程序员看的提示,不强制执行:

def process(x: int) -> str:
return str(x)
# 完全可以传入任何类型,Python解释器不报错
result = process("hello") # 运行时报错:TypeError

TypeScript在编译时就能发现这个错误:

function process(x: number): string {
return String(x);
}
// 编译错误:Argument of type 'string' is not assignable to parameter of type 'number'
const result = process("hello");
// 场景:处理用户输入
function formatUsername(name: string): string {
return name.trim().toLowerCase();
}
// 类型检查确保传入的是字符串
formatUsername(123); // 编译错误!
formatUsername(" Alice "); // 编译通过,输出 "alice"

Python等效代码(无编译时保护):

def format_username(name: str) -> str:
return name.strip().lower()
# Python解释器允许,但运行时报TypeError
format_username(123) # TypeError: 'int' object has no attribute 'strip'

TypeScript支持渐进式迁移:

// Step 1: 纯JavaScript(any类型)
function add(a, b) {
return a + b;
}
// Step 2: 部分添加类型
function add(a: number, b: number) {
return a + b;
}
// Step 3: 完整类型
const add: (a: number, b: number) => number = (a, b) => {
return a + b;
};

2.2 基础类型:string、number、boolean

Section titled “2.2 基础类型:string、number、boolean”

TypeScript:

const name: string = "Alice";
const greeting: string = 'Hello';
const template: string = `Hi, ${name}!`;
name = 123; // 错误!类型不匹配

Python对比:

name: str = "Alice"
greeting: str = 'Hello'
template: str = f"Hi, {name}!"

字符串常用操作:

const str: string = "Hello, TypeScript";
// 属性访问(只读)
str[0]; // "H"
str.charAt(0); // "H"
// 查找
str.indexOf("Type"); // 7
str.includes("abc"); // false
str.startsWith("Hello"); // true
str.endsWith("Script"); // true
// 修改(返回新字符串,原字符串不变)
str.toUpperCase(); // "HELLO, TYPESCRIPT"
str.toLowerCase(); // "hello, typescript"
str.trim(); // "Hello, TypeScript"
str.trimStart(); // "Hello, TypeScript "(去尾部空格)
str.trimEnd(); // " Hello, TypeScript"(去首部空格)
// 切片/分割
str.slice(0, 5); // "Hello"
str.substring(0, 5); // "Hello"
str.split(", "); // ["Hello", "TypeScript"]
str.replace("TypeScript", "World"); // "Hello, World"
str.concat("!"); // "Hello, TypeScript!"
// 重复
str.repeat(2); // "Hello, TypeScriptHello, TypeScript"
str.padStart(20, "-"); // "---Hello, TypeScript"
str.padEnd(20, "-"); // "Hello, TypeScript---"
// 长度
str.length; // 18

Python对比:

s = "Hello, TypeScript"
s[0] # "H"
s.index("Type") # 7
s.upper() # "HELLO, TYPESCRIPT"
s.split(", ") # ["Hello", "TypeScript"]
s.replace("TypeScript", "World") # "Hello, World"
len(s) # 18

TypeScript:

const integer: number = 42;
const float: number = 3.14;
const negative: number = -10;
const hex: number = 0xFF; // 255
const binary: number = 0b1010; // 10
const octal: number = 0o777; // 511
const bigInt: bigint = 1000000000000000000n; // BigInt

Python对比:

integer: int = 42
float_num: float = 3.14
negative: int = -10
hex_num: int = 0xFF # 255
binary_num: int = 0b1010 # 10
octal_num: int = 0o777 # 511
big_int: int = 10**18 # Python整数支持任意精度

数字常用操作:

// 算术运算
Math.abs(-5); // 5
Math.floor(3.7); // 3
Math.ceil(3.2); // 4
Math.round(3.5); // 4
Math.trunc(3.9); // 3
Math.sqrt(16); // 4
Math.pow(2, 10); // 1024
Math.max(1, 5, 3); // 5
Math.min(1, 5, 3); // 1
// 常量
Math.PI; // 3.141592653589793
Math.E; // 2.718281828459045
// 特殊值
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324
Number.NaN; // NaN
Number.POSITIVE_INFINITY; // Infinity
Number.NEGATIVE_INFINITY; // -Infinity
// 字符串转数字
parseInt("42"); // 42
parseInt("42.5"); // 42
parseFloat("3.14"); // 3.14
Number("42"); // 42
Number("42.5"); // 42.5
Number("hello"); // NaN
// 数字转字符串
String(42); // "42"
(42).toString(); // "42"
(42).toFixed(2); // "42.00"
(3.14159).toPrecision(4); // "3.142"
(255).toString(16); // "ff"

Python对比:

import math
abs(-5) # 5
math.floor(3.7) # 3
math.ceil(3.2) # 4
round(3.5) # 4
math.sqrt(16) # 4.0
max(1, 5, 3) # 5
min(1, 5, 3) # 1
# 常量
math.pi # 3.141592653589793
math.e # 2.718281828459045
# 字符串转数字
int("42") # 42
int("42.5") # ValueError!
float("3.14") # 3.14
int("hello") # ValueError!
# 数字转字符串
str(42) # "42"
format(42, ".2f") # "42.00"
format(3.14159, ".4f") # "3.1416"
hex(255) # '0xff'

浮点数精度问题:

0.1 + 0.2; // 0.30000000000000004(经典问题)
(0.1 + 0.2 === 0.3); // false!
// 解决方案:使用整数运算或toFixed
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON; // true
parseFloat((0.1 + 0.2).toFixed(2)); // 0.3

Python中同样的问题:

0.1 + 0.2 # 0.30000000000000004

TypeScript:

const isActive: boolean = true;
const isLoggedIn: boolean = false;
// 布尔运算
true && false; // false
true || false; // true
!true; // false
// 短路求值
isActive && console.log("Active!");
false && someFunction(); // 不执行someFunction
true || someFunction(); // 不执行someFunction

Python对比:

is_active: bool = True
is_logged_in: bool = False
# 布尔运算
True and False # False
True or False # True
not True # False
# 短路求值
if is_active:
print("Active!")

Truthy与Falsy:

TypeScript/JavaScript中,以下值在布尔上下文中被视为false(falsy):

  • false
  • 0、-0、0n
  • ""(空字符串)
  • null
  • undefined
  • NaN

其他所有值都是true(truthy)。

if ("") {
console.log("empty string is truthy");
} // 不会执行
if (0) {
console.log("0 is truthy");
} // 不会执行
if ("hello") {
console.log("non-empty string is truthy");
} // 会执行
if ([]) {
console.log("empty array is truthy");
} // 会执行!数组即使是空的也是truthy

Python对比:

# Python中,以下值是falsy
False
None
0
0.0
[]
{}
""
set()
# 其他都是truthy
if "hello":
print("truthy") # 执行
if []:
print("empty list is falsy") # 不执行
function printInfo(value: string | number | boolean): void {
if (typeof value === "string") {
console.log(`String: ${value.toUpperCase()}`);
} else if (typeof value === "number") {
console.log(`Number: ${value.toFixed(2)}`);
} else {
console.log(`Boolean: ${value}`);
}
}
printInfo("hello"); // String: HELLO
printInfo(42); // Number: 42.00
printInfo(true); // Boolean: true

2.3 类型推断:你不需要处处标注类型

Section titled “2.3 类型推断:你不需要处处标注类型”

TypeScript的编译器能够根据变量的赋值上下文自动推断变量类型。这称为”类型推断”(Type Inference)。

// 显式标注
let name: string = "Alice";
// 类型推断(等价)
let name2 = "Alice"; // TypeScript推断为string
name2 = 123; // 错误!不能将number赋值给string
// 函数返回类型推断
function add(a: number, b: number) {
return a + b; // 推断返回类型为number
}
const result = add(1, 2); // result是number
// 数组类型推断
const numbers = [1, 2, 3]; // number[]
const names = ["Alice", "Bob"]; // string[]
const mixed = [1, "hello"]; // (number | string)[]
// 箭头函数的参数类型可以从上下文推断
const double: (x: number) => number = x => x * 2;
// 等价于
const double2 = (x: number): number => x * 2;
// 对象字面量的类型推断
const person = {
name: "Alice",
age: 30
};
person.name = "Bob"; // OK
person.age = "thirty"; // 错误!age是number
// 初始值为any时,无法推断具体类型
let value = null; // any
value = 123; // any
value = "hello"; // any
// 声明但未初始化,无法推断
let flag; // any
flag = true; // OK
flag = "yes"; // OK
// 最佳实践:声明时初始化,或显式标注类型
let flag: boolean; // 显式标注
flag = true; // OK

Python对比:

# Python不需要类型标注,完全动态
value = None
value = 123
value = "hello"
# Python不做任何类型推断检查
flag = True
flag = "yes" # 完全OK,Python不报错
  1. 让TypeScript推断简单变量类型

    // 推荐:类型推断
    const name = "Alice";
    const count = 10;
    const isValid = true;
    // 不推荐:显式标注简单类型
    const name: string = "Alice";
  2. 显式标注复杂类型

    // 推荐:标注函数参数和返回值
    function process(data: string[]): number {
    return data.reduce((acc, s) => acc + s.length, 0);
    }
    // 推荐:标注复杂对象
    interface User {
    name: string;
    age: number;
    }
    const user: User = { name: "Alice", age: 30 };
  3. 使用类型断言处理已知类型

    // 当TypeScript无法推断时,可以手动断言
    const input = document.getElementById("myInput") as HTMLInputElement;
    const value = input.value; // TypeScript知道是string

// 基础类型
let name: string = "Alice";
let age: number = 30;
let isStudent: boolean = false;
// 数组
let scores: number[] = [95, 87, 92];
let names: Array<string> = ["Alice", "Bob"]; // 泛型语法
// 元组
let coordinate: [number, number] = [10, 20];
// 对象
let user: { name: string; age: number } = {
name: "Alice",
age: 30
};
// 函数声明
function add(a: number, b: number): number {
return a + b;
}
// 箭头函数
const multiply = (x: number, y: number): number => x * y;
// 可选参数
function greet(name: string, greeting?: string): string {
return greeting ? `${greeting}, ${name}!` : `Hello, ${name}!`;
}
// 默认参数
function createUser(name: string, age: number = 18): User {
return { name, age };
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
// 返回void(不返回值的函数)
function logMessage(message: string): void {
console.log(message);
}
// 返回never(永不返回的函数)
function throwError(message: string): never {
throw new Error(message);
}
// 函数类型
let myAdd: (x: number, y: number) => number;
myAdd = (a, b) => a + b;
myAdd = function(a, b) { return a + b; };
// 类型别名
type UserName = string;
type UserAge = number;
type Point = {
x: number;
y: number;
};
// 接口
interface User {
name: string;
age: number;
email?: string; // 可选属性
}
// 联合类型
type Status = "pending" | "approved" | "rejected";
type ID = string | number;
// 交叉类型
type Employee = User & {
employeeId: string;
department: string;
};
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42);
const str = identity<string>("hello");
// 泛型接口
interface Container<T> {
value: T;
getValue(): T;
}
// 泛型类
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
get(): T {
return this.content;
}
}
const numberBox = new Box<number>(42);
const stringBox = new Box<string>("hello");
type World = "world";
type Greeting = `hello, ${World}`; // "hello, world"
type Email = `${string}@${string}.${string}`;
type HttpsUrl = `https://${string}`;
function createUrl(path: string): `/${string}` {
return `/${path}`;
}

2.5 Python的type hint与TypeScript的类型系统对比

Section titled “2.5 Python的type hint与TypeScript的类型系统对比”

Python type hint:

from typing import List, Optional, Callable
def process(
name: str,
count: int,
items: List[str],
callback: Optional[Callable[[int], None]] = None
) -> List[int]:
results = [len(item) * count for item in items]
if callback:
callback(sum(results))
return results

TypeScript类型注解:

function process(
name: string,
count: number,
items: string[],
callback?: (sum: number) => void
): number[] {
const results = items.map(item => item.length * count);
if (callback) {
callback(results.reduce((a, b) => a + b, 0));
}
return results;
}
PythonTypeScript说明
intnumber整数和浮点数统一
floatnumber无区分
strstring字符串
boolboolean布尔值
list[int]number[]数组
dict[str, int]Record<string, number>字典
tuple[int, str][number, string]元组
Optional[int]number | undefined可选值
Union[int, str]number | string联合类型
Callable[[int], str](x: number) => string函数类型
Nonenull / undefined空值

Python type hint不强制执行:

def double(x: int) -> int:
return x * 2
double("hello") # 运行时报错:TypeError
double([1, 2]) # 运行时报错:TypeError

TypeScript编译时检查:

function double(x: number): number {
return x * 2;
}
double("hello"); // 编译错误!
double([1, 2]); // 编译错误!

Python:

from typing import TypeAlias
UserId: TypeAlias = int | str
Point: TypeAlias = tuple[float, float]
# 或使用TypeAlias注解(旧写法)
from typing import Union, Tuple
UserId = Union[int, str]
Point = Tuple[float, float]

TypeScript:

type UserId = number | string;
type Point = [number, number];

Python泛型(3.9+):

from typing import TypeVar
T = TypeVar("T")
def identity(x: T) -> T:
return x
# 约束泛型
Comparable = TypeVar("Comparable", bound=int | float)
def maximum(a: Comparable, b: Comparable) -> Comparable:
return a if a > b else b

TypeScript泛型:

function identity<T>(x: T): T {
return x;
}
// 约束泛型
function maximum<T extends number | string>(a: T, b: T): T {
return a > b ? a : b;
}
// 泛型约束
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): number {
return arg.length;
}

Python:需要额外工具如mypy、pyright

Terminal window
pip install mypy
mypy my_script.py

TypeScript:内置编译器检查

Terminal window
tsc --noEmit my_script.ts

2.6 any与unknown:类型安全的双重保障

Section titled “2.6 any与unknown:类型安全的双重保障”

any类型代表”任意类型”,使用它可以绕过所有类型检查。

let value: any = 123;
value = "hello";
value = [1, 2, 3];
value(); // 完全合法,TypeScript不报错
value.name; // 完全合法

何时使用any:

  • 逐步迁移JavaScript代码到TypeScript
  • 无法确定变量类型时
  • 第三方库返回复杂类型

为何慎用any:

  • 丧失TypeScript的类型安全优势
  • 错误只能在运行时发现
  • IDE无法提供准确的代码补全

unknown是any的类型安全版本。使用前必须先进行类型检查。

let value: unknown = 123;
value = "hello";
value = [1, 2, 3];
// 使用前必须类型检查
if (typeof value === "string") {
console.log(value.toUpperCase()); // OK
}
// 错误示例
value(); // 编译错误!不能调用unknown类型
value.name; // 编译错误!不能访问unknown属性
// 使用any(不推荐)
function parseJSON(json: string): any {
return JSON.parse(json);
}
const data = parseJSON('{"name": "Alice"}');
console.log(data.name); // 编译通过,但运行可能出错
// 如果JSON是'["a", "b"]',data.name是undefined
// 使用unknown(推荐)
function parseJSON(json: string): unknown {
return JSON.parse(json);
}
const data = parseJSON('{"name": "Alice"}');
// 安全地检查和使用
if (typeof data === "object" && data !== null && !Array.isArray(data)) {
if ("name" in data && typeof data.name === "string") {
console.log(data.name); // 安全的
}
}
function process(value: unknown): void {
// 1. typeof检查基础类型
if (typeof value === "string") {
console.log(value.toUpperCase());
}
// 2. instanceof检查类实例
else if (value instanceof Date) {
console.log(value.toISOString());
}
// 3. in检查属性存在
else if (typeof value === "object" && value !== null && "name" in value) {
console.log((value as { name: string }).name);
}
// 4. Array.isArray检查数组
else if (Array.isArray(value)) {
console.log(`Array with ${value.length} elements`);
}
// 5. 类型谓词函数
else if (isUser(value)) {
console.log(value.name);
}
}
// 自定义类型谓词
function isUser(obj: unknown): obj is { name: string; age: number } {
return (
typeof obj === "object" &&
obj !== null &&
"name" in obj &&
"age" in obj &&
typeof (obj as any).name === "string" &&
typeof (obj as any).age === "number"
);
}
特性anyunknown
类型安全无有
赋值给其他类型都可以必须类型检查后才能赋值
调用方法都可以不可以
访问属性都可以不可以
推荐使用场景快速原型、迁移JS代码处理外部数据、API响应

{
"compilerOptions": {
"strict": true
}
}

strict: true 启用所有严格类型检查选项,是最佳实践。

不允许隐式any类型。

// 错误:参数没有显式类型,且无法推断
function process(x) { // Error: Parameter 'x' implicitly has an 'any' type
return x;
}
// 正确:显式标注或让TypeScript推断
function process(x: number) {
return x;
}

Python无对应概念,因为Python中所有参数都可以是任意类型。

严格检查null和undefined。

// 错误:可能访问null/undefined的属性
function getLength(str: string | null): number {
return str.length; // Error: 'str' is possibly 'null'
}
// 正确:先检查null
function getLength(str: string | null): number {
if (str === null) {
return 0;
}
return str.length;
}
// 或者使用可选链
function getLength(str: string | null): number {
return str?.length ?? 0;
}

Python对比:

def get_length(s: str | None) -> int:
if s is None:
return 0
return len(s)

函数必须对所有代码路径有返回值。

// 错误:不是所有路径都返回值
function getGrade(score: number): string {
if (score >= 90) {
return "A";
} else if (score >= 80) {
return "B";
}
// Error: Not all code paths return a value
}
// 正确:所有路径都有返回值
function getGrade(score: number): string {
if (score >= 90) {
return "A";
} else if (score >= 80) {
return "B";
} else if (score >= 70) {
return "C";
} else {
return "F";
}
}

类的属性必须在构造函数中初始化。

class User {
name: string; // Error: Property 'name' has no initializer
age: number;
constructor(age: number) {
this.age = age;
// Error: Property 'name' must be initialized
}
}
// 正确:初始化所有属性
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// 或者使用确定赋值断言
class User {
name!: string; // 告诉TS我会赋值
age: number;
constructor(age: number) {
this.age = age;
this.name = "Unknown";
}
}
// 或者可选属性
class User {
name?: string;
age: number;
}

Python无此要求,因为Python的属性可以动态添加。

关闭严格模式后,TypeScript的行为更接近JavaScript:

// strictNullChecks: false 时
function getLength(str: string | null): number {
return str.length; // OK! 可能运行时错误
}
// noImplicitAny: false 时
function process(x) { // OK! x是隐式any
return x * 2;
}
// strictPropertyInitialization: false 时
class User {
name: string; // OK! 不要求初始化
}

如果项目已有大量代码,可以逐步启用严格模式:

{
"compilerOptions": {
"strict": false,
"noImplicitAny": true,
"strictNullChecks": true
}
}

然后逐步添加其他选项:

{
"compilerOptions": {
"strict": false,
"noImplicitAny": true,
"strictNullChecks": true,
"strictPropertyInitialization": true
}
}

直到完全启用strict: true。


本章深入学习了TypeScript的类型系统:

  1. 静态类型 vs 动态类型:

    • TypeScript在编译时检查类型,Python主要靠运行时
    • TypeScript提供可选的静态类型
  2. 基础类型:

    • string、number、boolean是三种基础类型
    • 与Python的str、int/float、bool对应
    • TypeScript的数字不区分整数和浮点
  3. 类型推断:

    • TypeScript能自动推断简单变量类型
    • 函数返回值、数组元素类型都能推断
    • 复杂类型建议显式标注
  4. 类型注解:

    • 变量、函数参数、函数返回值都可以显式标注
    • 接口和类型别名用于复杂类型
    • 泛型提供灵活的代码复用
  5. Python type hint对比:

    • Python的type hint仅供阅读,不强制
    • TypeScript的类型检查是编译时的
    • 两者语法和概念有很多相似之处
  6. any vs unknown:

    • any绕过所有类型检查,慎用
    • unknown要求使用前进行类型检查,更安全
    • 优先使用unknown而不是any
  7. 严格模式:

    • strict: true启用所有严格检查
    • 可逐步启用各个选项
    • 建议在项目中完全启用严格模式

创建以下变量并标注类型:

  • 一个字符串变量存储用户名
  • 一个数字变量存储年龄
  • 一个布尔变量存储是否登录
  • 一个数组存储多个分数

编写以下函数,带完整类型标注:

  • formatDate(date: Date): string - 返回格式化的日期字符串
  • filterByStatus(items: {name: string, status: string}[], status: string): {name: string, status: string}[] - 按状态过滤数组

尝试以下实验:

  1. 声明一个数组const arr = [1, 2, 3],查看TypeScript推断的类型
  2. 声明一个函数const add = (a, b) => a + b,查看参数类型
  3. 修改arr为arr.push("hello"),观察编译错误

编写一个函数safeParse(json: string): unknown,解析JSON并安全返回。然后在另一个函数中安全地使用返回结果。

  1. 在项目中创建一个tsconfig.strict.json,启用所有严格选项
  2. 尝试编译现有代码,观察有哪些错误
  3. 尝试修复这些错误


下一章我们将学习TypeScript的控制流与函数,掌握函数的各种声明方式、参数类型和返回类型,以及与Python函数的异同。