如何在fp-ts的pipe中批量绑定属性?解决TS(2556)报错
解决fp-ts批量绑定属性到Do作用域的TypeScript报错
问题重现
想要一次性把对象的多个属性通过fp-ts的bind()绑定到Do作用域,代码能正常运行,但TypeScript抛出以下错误:
A spread argument must either have a tuple type or be passed to a rest parameter.ts(2556)
原代码如下:
import * as A from "fp-ts/Array"; import * as IO from "fp-ts/IO"; const wrap2 = (x: unknown) => () => () => x; const wrap3 = (x: unknown) => () => () => () => x; const bindIOEntry = (entry: [string, unknown]) => IO.bind(entry[0], wrap3(entry[1])); const person = { title: "Mr.", name: "John", surname: "Doe", }; const result = pipe( IO.Do, ...A.map(bindIOEntry)(Object.entries(person)), IO.bind("tag", ({ title, name, surname }) => wrap2(title() + " " + name() + " " + surname()) ) ); console.log(result().tag()); // Mr. John Doe
问题原因
pipe的参数需要是固定长度的元组类型,而A.map(bindIOEntry)(Object.entries(person))返回的是动态长度的数组,TypeScript无法推断数组元素与pipe后续参数的类型兼容性,因此抛出报错。
解决方案
方案1:用reduce串联所有bind操作
把批量绑定逻辑用reduce封装,替代pipe中的spread操作,让TypeScript能正确跟踪类型:
import * as IO from "fp-ts/IO"; import { pipe } from "fp-ts/function"; const wrap2 = (x: unknown) => () => () => x; const wrap3 = (x: unknown) => () => () => () => x; const person = { title: "Mr.", name: "John", surname: "Doe", }; const result = pipe( // 通过reduce逐步把每个属性绑定到IO.Do上 Object.entries(person).reduce( (io, [key, value]) => IO.bind(key, wrap3(value))(io), IO.Do ), IO.bind("tag", ({ title, name, surname }) => wrap2(title() + " " + name() + " " + surname()) ) ); console.log(result().tag()); // Mr. John Doe
方案2:封装通用批量绑定函数
如果需要多次使用批量绑定,可以封装一个复用性更强的辅助函数:
import * as IO from "fp-ts/IO"; import { pipe } from "fp-ts/function"; const wrap2 = (x: unknown) => () => () => x; const wrap3 = (x: unknown) => () => () => () => x; // 通用批量绑定函数,接收对象并返回IO的串联操作 const bindAllIO = <Obj extends Record<string, unknown>>(obj: Obj) => (initialIO: IO.IO<{}>) => Object.entries(obj).reduce( (accIO, [key, val]) => IO.bind(key, wrap3(val))(accIO), initialIO as IO.IO<{ [K in keyof Obj]: ReturnType<typeof wrap3<Obj[K]>> }> ); const person = { title: "Mr.", name: "John", surname: "Doe", }; const result = pipe( IO.Do, bindAllIO(person), IO.bind("tag", ({ title, name, surname }) => wrap2(title() + " " + name() + " " + surname()) ) ); console.log(result().tag()); // Mr. John Doe
关键说明
两种方案都避免了在pipe中直接spread动态数组,而是通过reduce将多个bind操作逐步组合,让TypeScript能够准确推断作用域中的属性类型,既解决了报错,又保证了类型安全。
内容的提问来源于stack exchange,提问作者Adam B.
相关产品推荐
相关产品推荐

