关于 functional-constructs-types
这项 TypeScript 技能介绍 map、filter、reduce 等写法如何帮助类型推断,适合处理数据转换并在函数式方法与循环之间做选择的开发者。
主要功能
- 01优先使用 map、filter 和 reduce
- 02在链式操作中保留类型推断
- 03转换并聚合集合
- 04通过 filter 类型守卫缩窄类型
- 05识别适合使用循环的情况
适用场景
- 01替换构建数组的循环
- 02转换嵌套数组
- 03缩窄集合中的联合类型
- 04为性能敏感代码选择写法
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill functional-constructs-types安装后在 Claude Code 中输入/functional-constructs-types确认已加载。
详细介绍
使用函数式构造和库,让类型自然流动
概述
相比命令式循环,函数式编程构造(map、filter、reduce)与 TypeScript 配合得更好。
TypeScript 的类型推断尤其适用于函数式构造。它们会生成自然传递的中间类型,而循环则要求你手动跟踪类型。
何时使用此技巧
- 使用 for 循环构建数组
- 转换数据结构
- 类型无法在命令式代码中传递
- 在循环和函数式方法之间做选择
铁律
Prefer map, filter, and reduce over for loops.
Types flow naturally through functional chains.
请记住:
- 函数式方法会返回带类型的值
- 循环需要手动管理类型
- 链式调用会保留类型上下文
- Lodash 等库拥有出色的类型支持
识别:循环中的类型问题
// Loop: type must be declared or evolves
const result: string[] = [];
for (const item of items) {
result.push(item.name);
}
// What if you forget the annotation?
const result = []; // any[]
for (const item of items) {
result.push(item.name);
}
result
// ^? any[] - type information lost
函数式解决方案
const result = items.map(item => item.name);
// ^? string[] - type inferred automatically
TypeScript 会根据输入类型和映射函数推断输出类型。
链式调用中的类型流动
const namesOfAdults = people
.filter(p => p.age >= 18)
// ^? Person[]
.map(p => p.name)
// ^? string[]
.sort()
// ^? string[]
.join(', ');
// ^? string
每个步骤都有明确定义的类型,并由 TypeScript 跟踪。
常见转换
map:转换每个元素
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
// ^? number[]
const users = [{ name: 'Alice', age: 30 }];
const names = users.map(u => u.name);
// ^? string[]
filter:保留符合条件的元素
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0);
// ^? number[]
// With type guard for narrowing
const mixed: (string | number)[] = [1, 'a', 2, 'b'];
const strings = mixed.filter((x): x is string => typeof x === 'string');
// ^? string[]
reduce:聚合为单个值
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, n) => acc + n, 0);
// ^? number
const grouped = items.reduce((acc, item) => {
const key = item.category;
acc[key] = acc[key] || [];
acc[key].push(item);
return acc;
}, {} as Record<string, Item[]>);
// Note: reduce sometimes needs type hints
flatMap:映射并扁平化
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.flatMap(arr => arr);
// ^? number[]
const sentences = ['Hello world', 'TypeScript rocks'];
const words = sentences.flatMap(s => s.split(' '));
// ^? string[]
对象转换
Object.entries / Object.fromEntries
const obj = { a: 1, b: 2, c: 3 };
// Transform values
const doubled = Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, v * 2])
);
// ^? { [k: string]: number }
// Filter entries
const filtered = Object.fromEntries(
Object.entries(obj).filter(([k, v]) => v > 1)
);
Record 转换
type Input = Record<string, number>;
type Output = Record<string, string>;
const input: Input = { a: 1, b: 2 };
const output: Output = Object.fromEntries(
Object.entries(input).map(([k, v]) => [k, String(v)])
);
Lodash 和类型友好型库
import _ from 'lodash';
const grouped = _.groupBy(users, 'department');
// ^? Dictionary<User[]>
const sorted = _.sortBy(users, ['lastName', 'firstName']);
// ^? User[]
const unique = _.uniqBy(users, 'id');
// ^? User[]
Lodash 对 TypeScript 的支持非常出色。
适合使用循环的情况
对性能要求极高的代码
// Loop might be faster for very large arrays
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
提前退出
// find is functional, but loops can break early
function findFirst<T>(arr: T[], pred: (x: T) => boolean): T | undefined {
for (const item of arr) {
if (pred(item)) return item;
}
return undefined;
}
// Or just use: arr.find(pred)
复杂的变更操作
// Some algorithms are clearer with loops
function quickSort<T>(arr: T[]): T[] {
// ... loop-based implementation
}
将循环转换为函数式写法
// Before: loop with accumulator
const result: ProcessedItem[] = [];
for (const item of items) {
if (item.isValid) {
result.push(processItem(item));
}
}
// After: filter + map
const result = items
.filter(item => item.isValid)
.map(item => processItem(item));
抵御压力的准则
1. “循环更易读”
压力:“我更熟悉 for 循环”
**回应:**函数式方法能清晰表达意图:map = 转换,filter = 选择,reduce = 聚合。
**行动:**学习这些模式。很快就能熟练掌握。
2. “性能方面的顾虑”
压力:“多次遍历会更慢”
**回应:**对于大多数数据规模,代码清晰比微优化更重要。
**行动:**先分析性能,再进行优化。大多数代码并非性能关键代码。
危险信号——停止并重新考虑
const result = []后接一个用于推入元素的循环- 只有因为使用循环才需要类型标注
- 在循环中复杂地跟踪状态
- 本应声明得更具体,却使用了
any[]
常见借口(均不成立)
| 借口 | 事实 |
|---|---|
| “循环更简单” | 函数式方法能更清晰地表达意图 |
| “我需要索引” | .map((item, i) => ...) 会提供索引 |
| “多次遍历会很慢” | 通常影响不大;先进行测量 |
快速参考
// DON'T: Loop with manual type
const result: string[] = [];
for (const x of items) {
result.push(x.name);
}
// DO: Functional with inferred type
const result = items.map(x => x.name);
// Filter + Map
const processed = items
.filter(x => x.isValid)
.map(x => transform(x));
// Type guard in filter
const strings = mixed.filter((x): x is string => typeof x === 'string');
// Reduce (with type hint when needed)
const grouped = items.reduce((acc, x) => ..., {} as GroupedType);
总结
函数式构造能让类型自然流动。
map、filter、reduce 等方法无需手动标注即可生成类型正确的结果。它们能清晰表达转换,并且与 TypeScript 的类型推断配合出色。只有在有明确理由时才使用循环。
参考
基于 Dan Vanderkam 所著的《Effective TypeScript》,第 26 条:使用函数式构造和库,让类型自然流动。
评论
支持 Markdown