⚡ 函数类型注解
TypeScript 允许你为函数的参数和返回值添加类型注解,使函数签名成为一份明确的契约。
普通函数写法:
function add(a: number, b: number): number {
return a + b;
}
function greet(name: string): string {
return `你好,${name}!`;
}
// 返回 void 表示没有返回值
function log(message: string): void {
console.log(message);
}
等价的箭头函数写法:
const add = (a: number, b: number): number => a + b;
const greet = (name: string): string => `你好,${name}!`;
// 多行箭头函数
const processData = (input: string): string[] => {
const parts = input.split(",");
return parts.map(p => p.trim());
};
💡 类型推断:
如果函数体很简单,TypeScript 能自动推断返回类型。但对于公共 API 和库函数,显式标注返回类型是最佳实践——它既是文档,也防止意外修改返回值类型。
❓ 可选参数与默认值
用 ? 标记可选参数,用 = 设置默认值,用 ...args 收集剩余参数。
// 可选参数:middleName 可以不传
function fullName(first: string, last: string, middleName?: string): string {
return middleName ? `${first} ${middleName} ${last}` : `${first} ${last}`;
}
fullName("张", "三"); // "张 三"
fullName("张", "三", "小明"); // "张 小明 三"
// 默认值:有默认值的参数自动变为可选
function createUrl(path: string, base: string = "https://api.example.com"): string {
return `${base}${path}`;
}
createUrl("/users"); // "https://api.example.com/users"
createUrl("/users", "http://localhost"); // "http://localhost/users"
// 剩余参数:收集任意数量的参数
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4, 5); // 15
⚠️ 注意:
可选参数必须放在必选参数之后。如果你需要中间的参数可选,考虑使用对象参数或函数重载。
🔀 函数重载
当一个函数根据不同的输入类型返回不同的类型时,可以使用重载签名来精确描述这种行为。
// 重载签名(仅声明,不实现)
function parse(input: string): number;
function parse(input: number): string;
// 实现签名(必须兼容所有重载)
function parse(input: string | number): number | string {
if (typeof input === "string") {
return parseInt(input, 10);
}
return input.toString();
}
const num = parse("42"); // 类型推断为 number
const str = parse(42); // 类型推断为 string
更实用的例子——根据参数数量重载:
function createElement(tag: string): HTMLElement;
function createElement(tag: string, content: string): HTMLElement;
function createElement(tag: string, content?: string): HTMLElement {
const el = document.createElement(tag);
if (content) el.textContent = content;
return el;
}
💡 重载 vs 联合类型:
如果输入/输出类型之间没有对应关系,直接用联合类型更简洁。只有当"输入 A → 返回 X,输入 B → 返回 Y"这种映射关系需要精确表达时,才使用重载。
📐 interface 接口
接口用于描述对象的形状(shape),是 TypeScript 最核心的类型构建工具之一。
interface User {
id: number;
name: string;
email: string;
age?: number; // 可选属性
readonly createdAt: Date; // 只读属性
}
const user: User = {
id: 1,
name: "张三",
email: "zhang@example.com",
createdAt: new Date(),
};
// user.createdAt = new Date(); // ❌ 编译错误:readonly 不可修改
索引签名——当你不确定属性名但知道值类型时:
interface StringMap {
[key: string]: string;
}
const headers: StringMap = {
"Content-Type": "application/json",
"Authorization": "Bearer token123",
};
interface NumberArray {
[index: number]: string;
length: number;
}
🔄 与 Java 的区别:
Java 用 interface 定义行为契约,类需要 implements 显式声明实现。TypeScript 的 interface 是纯结构检查(duck typing),对象只要结构匹配就自动兼容——不需要任何显式声明。这意味着你可以为第三方库的对象定义接口,而无需修改其源代码。
🧬 接口继承与组合
接口可以通过 extends 继承其他接口,支持多继承。
interface HasId {
id: number;
}
interface HasTimestamps {
createdAt: Date;
updatedAt: Date;
}
// 继承多个接口
interface User extends HasId, HasTimestamps {
name: string;
email: string;
}
// 等价于手写所有属性:
// interface User {
// id: number;
// createdAt: Date;
// updatedAt: Date;
// name: string;
// email: string;
// }
const user: User = {
id: 1,
name: "李四",
email: "li@example.com",
createdAt: new Date(),
updatedAt: new Date(),
};
💡 声明合并:
同名的 interface 会自动合并属性(declaration merging)。这在扩展第三方库的类型定义时非常有用,但也容易造成意外——注意命名冲突。
🏷️ type 别名
type 关键字可以为任何类型创建别名,比 interface 更灵活。
// 基本别名
type ID = string | number;
type Status = "active" | "inactive" | "banned";
// 对象类型(与 interface 类似)
type Point = {
x: number;
y: number;
};
// 联合类型
type Result<T> = { success: true; data: T } | { success: false; error: string };
// 交叉类型(合并多个类型)
type WithId = { id: number };
type WithName = { name: string };
type Entity = WithId & WithName; // { id: number; name: string }
// 使用内置工具类型
type PartialUser = Partial<User>; // 所有属性变为可选
type ReadonlyUser = Readonly<User>; // 所有属性变为只读
type UserName = Pick<User, "name" | "email">; // 只取部分属性
🤔 type vs interface:如何选择?
- interface:描述对象形状时优先使用;支持声明合并和 extends 继承;更适合面向对象风格
- type:联合类型、交叉类型、元组、基本类型别名只能用 type;函数式编程风格更常用
- 实际项目中混用很常见,关键是团队保持一致
🔗 函数类型与回调
TypeScript 可以精确描述回调函数和高阶函数的类型签名。
// 用 type 定义函数类型
type Callback = (data: string) => void;
type Predicate<T> = (item: T) => boolean;
type Transformer<T, U> = (input: T) => U;
// 使用函数类型
function fetchData(url: string, onSuccess: Callback, onError: Callback): void {
// ...
}
// 高阶函数:接收函数,返回函数
function filter<T>(arr: T[], predicate: Predicate<T>): T[] {
return arr.filter(predicate);
}
const numbers = [1, 2, 3, 4, 5];
const evens = filter(numbers, n => n % 2 === 0); // [2, 4]
// 工厂函数模式
type Logger = (message: string) => void;
function createLogger(prefix: string): Logger {
return (message: string) => {
console.log(`[${prefix}] ${message}`);
};
}
const appLog = createLogger("APP");
appLog("启动完成"); // "[APP] 启动完成"
💡 接口也能描述函数:
interface SearchFunc {
(source: string, keyword: string): boolean;
}
const search: SearchFunc = (src, kw) => src.includes(kw);
但在实践中,用 type 定义函数类型更简洁,也更常见。
📝 本章要点
函数类型
参数和返回值都可标注类型;支持可选参数 ?、默认值 = 和剩余参数 ...
函数重载
多个重载签名 + 一个实现签名;用于精确描述输入输出的映射关系
interface
描述对象形状;支持可选/只读属性、索引签名、继承和声明合并
type 别名
比 interface 更灵活:联合类型、交叉类型、工具类型都需要 type
Duck Typing
TypeScript 的接口是结构化类型检查,无需显式 implements 声明
回调与高阶函数
用 type 定义函数签名类型,让回调参数获得完整的类型检查