mcpField
F
functional-constructs-types

functional-constructs-types

github.com/marius-townhouse开发643 次安装38 周前更新
代码仓库

关于 functional-constructs-types

这项 TypeScript 技能介绍 map、filter、reduce 等写法如何帮助类型推断,适合处理数据转换并在函数式方法与循环之间做选择的开发者。

主要功能

  1. 01优先使用 map、filter 和 reduce
  2. 02在链式操作中保留类型推断
  3. 03转换并聚合集合
  4. 04通过 filter 类型守卫缩窄类型
  5. 05识别适合使用循环的情况

适用场景

  1. 01替换构建数组的循环
  2. 02转换嵌套数组
  3. 03缩窄集合中的联合类型
  4. 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