☰
Remeda v2 迁移指南:createPipe 更名为 piped 的重命名解析与源码实现
2026/10/13 2:40:00 网站建设 项目流程
  • 开发工具

【免费下载链接】remeda

A utility library for JavaScript and TypeScript.

项目地址:https://gitcode.com/gh_mirrors/re/remeda
点击查看免费下载

Remeda 从 v1 升级到 v2 时对一批函数进行了重命名,createPipe是其中最典型的案例之一:它被更名为piped,调用方式几乎不变,但语义更清晰、更贴合 ECMAScript 与函数式编程的惯例。本文以 createPipe 迁移文档 为主线,结合 piped 源码、pipe 实现 与测试用例,说明这次重命名的背景、迁移改写方式,以及piped/pipe底层惰性求值的工作原理。读完本文,你将能快速完成存量代码中createPipe的替换,并理解新 API 为何更适合在map、filter等回调中组合多个变换。

一、迁移背景:v1 → v2 为何对 createPipe 重命名

在 Remeda 的文档体系中,v1 迁移说明被组织在packages/docs/src/content/v1-migration/目录下,每个被迁移的函数对应一个 Markdown 文件。createPipe.md全文只有一段核心信息:

#### Renamed Called `piped` instead.

也就是说,createPipe属于Renamed(重命名)类别,而不是被删除。更完整的迁移背景记录在 migration-removed.md 中,其中解释了重命名的总体原则:

Remeda 早期大量借鉴了 Lodash 和 Ramda 的命名,但这些名字并不总是与 ECMAScript 标准选择的名称一致,因此 Remeda 决定优先采用标准名称;同时,还通过去掉缩写和拼写不全的词,改用完整的英文单词来改进命名。

被重命名的完整函数清单为:createPipe、equals、fromPairs、isNil、toPairs、uniq、uniqBy和uniqWith。createPipe之所以改名,是因为它本质上是pipe的“无头(headless)”变体——先组合好一串函数、之后再喂入数据——用动词化的piped来命名,比“创建一个 pipe”更能准确表达其行为,也和pipe形成清晰的对应关系。

二、v1 时代的 createPipe 是什么

在 Remeda v1 中,createPipe是一个创建“数据后置(data-last)管道函数”的工具。v1 函数文档(保存在 functions.json 的 Typedoc 导出数据中)记录了它的签名与示例:

// 签名 R.createPipe(op1, op2, op3)(data); // 示例 R.createPipe( (x: number) => x * 2, x => x * 3, )(1); // => 6

注意这个签名分为两段:先传入一串操作函数(op1、op2、op3……),返回一个新的“等待数据的函数”;再对返回的函数传入data才会真正执行。这与直接调用pipe(data, op1, op2)恰好互补:pipe是数据优先(data-first),createPipe是数据后置。

从 v1 的 Typedoc 数据还能看到,createPipe当时已被标记为DEPRECATED,弃用提示为:

UseR.piped(op1, op2, op3). Will be removed in V2!

即官方早在 v1 末期就建议用户改用piped,为 v2 的移除做准备。

三、迁移改写:从 createPipe 到 piped

createPipe的迁移改动非常小,原迁移文档给出的示例是:

// Was createPipe(map(mapper), filter(predicate)); // Now piped(map(mapper), filter(predicate));

对比可见,替换规则就是把createPipe(...)直接换成piped(...),函数参数与调用结构完全不变——因为两者同样接收一串操作函数、同样返回一个接收数据的函数。迁移文档中该示例使用map(mapper)、filter(predicate)这类 Remeda 惯用的数据后置写法,正好说明了它的典型用法:先组合“变换 + 过滤”,再把结果作为回调交给其他函数使用。

再结合 v1 文档中的示例,可以整理出更完整的对照:

// Was:v1 写法 const doubleAndTriple = createPipe( (x: number) => x * 2, (x) => x * 3, ); doubleAndTriple(1); // => 6 // Now:v2 写法 const doubleAndTriple = piped( (x: number) => x * 2, (x) => x * 3, ); doubleAndTriple(1); // => 6

如果你的存量代码中createPipe返回的函数只被调用了一次,也可以直接内联成piped(...)(data)的形式,甚至进一步改写为pipe(data, ...)的数据优先写法。

四、piped 是什么:pipe 的 headless(数据后置)版本

piped的完整实现位于 packages/remeda/src/piped.ts,其 JSDoc 开篇就说明了定位:

Data-last version ofpipe. Seepipedocumentation for full details.

它的签名是:

piped(...functions)(data);

即“传入一串函数,返回一个等待data的函数”。其核心实现只有几行:

export function piped(...functions: readonly ((input: unknown) => unknown)[]) { return (value: unknown): unknown => pipe( value, // @ts-expect-error [ts2556] - pipe is typed for users and this is an internal function ...functions, ); }

也就是说,piped(...functions)内部就是调用数据优先的pipe(value, ...functions)——先收集函数列表,等拿到数据后再一次性交给pipe执行。这也解释了为什么piped与createPipe的调用结构可以无缝替换:两者都是“先组合、后喂数据”的封装。

为了让 TypeScript 获得精确的类型推断,piped.ts从源码层面提供了从 0 到 15 个函数的重载系列(piped<A>()、piped<A, B>(funcA)、piped<A, B, C>(funcA, funcB)……一直到piped<A, ..., P>(funcA, ..., funcO)),每个重载都把前一个函数的返回值类型作为下一个函数的输入类型,从而在编译期逐级推导出最终结果类型。从源码结构看,这一系列手写重载保证了piped在作为回调传递时也能保留完整的类型信息。

五、piped 的适用场景与使用限制

5.1 最典型的用法:作为 map、filter 的回调

piped的文档注释给出了明确的使用建议:当你需要把一个“变换管道”作为回调传给map、filter这类函数时,使用piped——因为此时输入数据类型可以从调用点推断出来。官方示例:

map( [{ a: 1 }, { a: 2 }, { a: 3 }], piped(prop('a'), add(1)), ); //=> [2, 3, 4]

这里piped(prop('a'), add(1))组合了“取a属性”和“加 1”两步,再整体作为map的变换回调。这种写法在仓库内部也被大量采用,例如 Lodash 映射文档 differenceBy.md 中:

filter(DATA, piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))));

以及在 negate.md(Ramda 侧 complement.md 同款)中:

isNot(piped(getLength, isTruthy));

可以看到,piped已经成为仓库内编写“回调内多步处理”的标准手法。

5.2 使用限制:不能当作“函数工厂”

piped.ts的 JSDoc 中有一条重要的IMPORTANT说明:piped不能用来当作“函数工厂”创建独立的工具函数,因为 TypeScript 无法在没有数据的情况下推断输入类型(除非你为管道中的每个函数显式声明全部类型参数)。官方推荐的做法是:

显式定义一个函数,并在其内部使用pipe实现。

// 不推荐:无法推断输入类型 const myTransform = piped(prop('a'), add(1)); // 输入类型不明确 // 推荐:显式定义函数,内部用 pipe const myTransform = (input: SomeType) => pipe(input, prop('a'), add(1));

这一点对迁移很有指导意义:如果createPipe之前是用于产出可复用的独立函数,迁移到piped后应改用“显式函数 + 内部pipe”的模式;如果只是作为内联回调,则直接换成piped即可。

六、底层原理:pipe 的惰性求值与提前终止

既然piped最终委托给pipe,理解pipe的实现就能掌握整套迁移后的执行语义。pipe的完整实现位于 packages/remeda/src/pipe.ts,核心机制是惰性求值(lazy evaluation)。

6.1 惰性步骤的构建

pipe运行时会先把每个函数包装成“惰性步骤”:

const lazySteps = functions.map((op) => "lazy" in op ? { lazyEvaluator: op.lazy(...op.lazyArgs), isSingle: op.lazy.single ?? false, index: 0, items: [], } : undefined, );

只有带有lazy标记的函数(如map、filter、take、drop、forEach等)才会被包装成惰性步骤。当连续多个惰性函数组合时,extractLazySequence会把它们聚合成一个“惰性序列”,数据在序列内逐项流过,而不是每个函数各自产出完整的中间数组。

6.2 逐项处理与提前终止

processIterable与processItem负责真正的逐项求值:输入可迭代对象中的每个元素依次经过惰性序列,processItem会在某个元素被过滤掉、或序列宣告完成(lazyResult.done)时提前返回。这就是pipe支持提前终止的原因——例如文档注释中的示例:

pipe( hugeArray, map(expensiveComputation), filter(complexPredicate), take(2), // 找到 2 个结果后立即停止,大部分元素根本不会被处理 );

take(2)标记为single步骤,一旦收集到足够的元素,整个processIterable的循环就会break,从而跳过剩余数据的昂贵计算。这正是pipe/piped相比“每步生成中间数组”的朴素实现更高效的关键设计。

6.3 惰性求值的启用条件

pipe.ts的 JSDoc 明确说明了启用条件:

Functions are only evaluated lazily when their>test("should pipe a single operation", () => { const fn = piped((x: number) => x * 2); expect(fn(1)).toBe(2); }); test("should pipe operations", () => { const fn = piped( (x: number) => x * 2, (x) => x * 3, ); expect(fn(1)).toBe(6); });

这两个用例分别验证了“单个操作”与“多操作串联”两种场景,确认piped返回的函数确实按从左到右的顺序执行组合后的变换。迁移后在遇到类型或行为疑问时,可以参考这些测试用例来验证自己的改写是否正确。

八、迁移落地建议

  1. 利用 v1 的弃用提示逐个替换:migration-removed.md 提到,Remeda v1 的后续版本已对所有被重命名/移除的函数标记了弃用并给出迁移建议,建议在仍处于 v1 时逐个完成替换,降低一次性升级 v2 的冲击。
  2. 内联回调直接替换:若createPipe(...)的结果仅作为map、filter等的回调,直接改为piped(...)即可,参数完全不动。
  3. 独立工具函数改为显式定义:若用于产出可复用的独立函数,改为“显式声明输入类型的函数 + 内部pipe”的写法,避免piped无法推断输入类型的问题。
  4. 查阅 v1 文档页确认改法:v1 函数文档页(由 migrate/v1.astro 渲染,迁移说明通过 v1/function-card.astro 中的 “Breaking changes in v2” 折叠面板展示)会为每个函数内联渲染对应的迁移条目,可直接对照。

createPipe更名为piped是 Remeda v2 重命名工作中改动最轻的一类迁移:调用结构零变化,只需替换函数名,同时还能免费获得pipe的惰性求值与提前终止机制。在迁移时唯一需要留意的,就是piped不能作为独立函数工厂使用——遇到这种场景,改用“显式函数 + 内部pipe”即可。

  • 开发工具

【免费下载链接】remeda

A utility library for JavaScript and TypeScript.

项目地址:https://gitcode.com/gh_mirrors/re/remeda
点击查看免费下载
上一篇:终极指南:如何一键将网页图片另存为JPG、PNG或WebP格式
下一篇:3分钟完成Figma中文界面汉化:新手设计师的终极解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询