关于 function-type-expressions
这项 TypeScript 技能介绍如何为整个函数表达式指定类型,减少重复标注并检查返回值。适用于编写回调、包装函数或签名相同的多个函数。
主要功能
- 01为整个函数表达式指定类型
- 02在相同签名间复用类型
- 03从函数类型推断参数类型
- 04按类型检查返回值
- 05用 typeof 匹配现有签名
适用场景
- 01为库提供的回调添加类型
- 02实现签名相同的多个函数
- 03包装函数并保持其参数签名
- 04减少相关函数中的重复标注
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill function-type-expressions安装后在 Claude Code 中输入/function-type-expressions确认已加载。
详细介绍
尽可能为整个函数表达式应用类型
概述
一次为整个函数指定类型,而不是分别为各个参数指定类型。
使用函数表达式(而非函数语句)时,可以为整个函数应用类型。这样可以减少重复、提高类型安全性,并让代码更易读。
何时使用此技能
- 编写多个签名相同的函数
- 为库实现回调
- 与现有函数的签名保持一致
- 包装或扩展现有函数
铁律
When functions share a signature, define the type ONCE and apply it to EACH function.
请记住:
- 可以为函数表达式应用类型
- 参数类型会根据函数类型推断
- 返回类型会根据函数类型进行检查
- 使用
typeof fn匹配现有函数签名
检测:重复的签名
// Repetitive - same signature 4 times
function add(a: number, b: number) { return a + b; }
function sub(a: number, b: number) { return a - b; }
function mul(a: number, b: number) { return a * b; }
function div(a: number, b: number) { return a / b; }
解决方案:函数类型
定义一次,多次使用
type BinaryFn = (a: number, b: number) => number;
const add: BinaryFn = (a, b) => a + b; // Types inferred
const sub: BinaryFn = (a, b) => a - b;
const mul: BinaryFn = (a, b) => a * b;
const div: BinaryFn = (a, b) => a / b;
优点:
- 无需重复标注类型
- 自动检查返回类型
- 没有类型噪声,逻辑更加清晰
使用 typeof 匹配现有函数
// Match fetch's signature exactly
const checkedFetch: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response;
};
TypeScript 会推断:
input: RequestInfo | URLinit?: RequestInit- 返回值:
Promise<Response>
在保留参数的同时更改返回类型
// Match parameters but change return type
async function fetchNumber(
...args: Parameters<typeof fetch>
): Promise<number> {
const response = await checkedFetch(...args);
return Number(await response.text());
}
函数语句与函数表达式
// Statement - must annotate each parameter
function rollDice1(sides: number): number { /* ... */ }
// Expression - can apply type to entire function
type DiceRollFn = (sides: number) => number;
const rollDice2: DiceRollFn = (sides) => { /* ... */ };
常见函数类型模式
回调类型
type EventHandler = (event: Event) => void;
type AsyncCallback<T> = () => Promise<T>;
type Comparator<T> = (a: T, b: T) => number;
const handleClick: EventHandler = (e) => {
console.log(e.target); // e is typed as Event
};
泛型函数类型
type Mapper<T, U> = (item: T, index: number) => U;
const double: Mapper<number, number> = (n) => n * 2;
const stringify: Mapper<number, string> = (n) => String(n);
接口语法(替代方案)
// Function type as interface
interface StringTransform {
(input: string): string;
}
const toUpper: StringTransform = s => s.toUpperCase();
返回类型安全性
函数类型可以捕获返回类型错误:
const checkedFetch: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (!response.ok) {
return new Error('Failed'); // Error!
// Type 'Error' is not assignable to type 'Response'
}
return response;
};
如果没有函数类型,只有在调用处才会报错。
库的回调类型
库通常会提供回调类型:
// React provides these
import { MouseEventHandler, ChangeEventHandler } from 'react';
const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {
console.log(e.currentTarget.disabled); // Fully typed
};
const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
console.log(e.target.value); // Fully typed
};
何时不使用函数类型
不要为单个函数过度设计:
// Overkill for a single standalone function
type GreetFn = (name: string) => string;
const greet: GreetFn = (name) => `Hello, ${name}`;
// Just use a normal function statement
function greet(name: string): string {
return `Hello, ${name}`;
}
抗压协议
1. “函数语句更清晰”
压力:“我更喜欢将类型直接写在参数旁边。”
**回应:**对于共享签名的函数,集中定义类型可以减少噪声。
**行动:**当有 2 个或更多函数共享签名时,使用函数类型。
2. “我不知道库的类型”
压力:“我找不到库中的回调类型。”
**回应:**使用 typeof existingFunction 或 Parameters<typeof fn>。
**行动:**使用 typeof 匹配现有签名。
危险信号 - 停下来重新考虑
- 多个函数重复使用相同的参数类型
- 应与被包装函数签名保持一致的包装函数
- 未正确指定类型的回调
常见借口(都不成立)
| 借口 | 实际情况 |
|---|---|
| “参数上的类型更清晰” | 重复 4 次时就不清晰了 |
| “函数语句更简单” | 带类型的函数表达式同样清晰 |
“我就用 any” | 会失去所有类型安全性优势 |
快速参考
// Define function type
type Transform<T> = (value: T) => T;
// Apply to function expression
const double: Transform<number> = v => v * 2;
// Match existing function
const myFetch: typeof fetch = async (input, init) => { ... };
// Match parameters, change return
function wrapper(...args: Parameters<typeof original>): NewReturn { ... }
归根结底
当多个函数共享签名时,为整个函数表达式应用类型。
这样可以减少重复、集中定义类型,并在源头捕获返回类型错误。使用 typeof 精确匹配现有函数签名。
参考
基于 Dan Vanderkam 所著的《Effective TypeScript》,第 12 条:尽可能为整个函数表达式应用类型。
评论
支持 Markdown