typescript - 经典的“省略”功能,类似的代码以通常的方式编写和使用柯里化编写时完全不同的打字结果,我错过了什么?
问题描述
好的,所以我试图编写一个类型感知的“省略”函数。
经过长时间阅读堆栈溢出后,我想出了以下可行的解决方案(耶):
const omit = <
T extends Record<string, unknown>,
Del extends keyof T,
U = { [Key in Exclude<keyof T, Del>]: T[Key] }
> (obj: T, ...props: Del[]): U =>
Object.entries(obj).reduce((acc, [key, value]): U => {
for (const del of props) {
if (del === key) {
return acc;
}
}
return { ...acc, [key]: value };
}, {} as U);
如果我写omit({ a: 1, b: 2 }, 'a');
,那么 tsc 非常了解它:
但我更喜欢编写这类东西的函数式编程方式,使用一个函数,该函数将 props 省略,然后返回一个函数,该函数将获取一个对象并在没有指定 props 的情况下返回它(对组合很有用)。
所以我试着这样写,它几乎是相同的代码:
const fpOmit = <
T extends Record<string, unknown>,
Del extends keyof T,
U = { [Key in Exclude<keyof T, Del>]: T[Key] }
> (...props: Del[]) => (obj: T): U =>
Object.entries(obj).reduce((acc, [key, value]): U => {
for (const del of props) {
if (del === key) {
return acc;
}
}
return { ...acc, [key]: value };
}, {} as U);
没有错误,没有警告,但是这次调用fpOmit('a')({ a: 1, b: 2 });
根本没有推断出预期的类型:
我在这里做错了什么?
解决方案
调用泛型函数时,必须指定其所有类型参数;由调用者手动(如fn<MyObjType, MyKeyType>(...)
)或通过从传递给函数的参数推断(有时,从预期的返回类型的上下文中推断)。
在您的原始omit()
功能中:
declare const omit: <T extends Record<string, unknown>, D extends keyof T>(
obj: T, ...props: D[]
) => { [K in Exclude<keyof T, D>]: T[K]; }
编译器可以从 和 参数中推断出和T
类型D
参数,并且一切正常:obj
props
const result = omit({ a: 1, b: 2 }, "a");
// const result: { b: number; }
但在你的咖喱版本中:
declare const fpOmit: <T extends Record<string, unknown>, D extends keyof T>(
...props: D[]) => (obj: T) => { [K in Exclude<keyof T, D>]: T[K]; }
当您在一行上写下以下内容时:
const fpResult = fpOmit('a')({ a: 1, b: 2 });
它仍然是一对函数调用,如下所示:
const omitA = fpOmit('a');
const fpResult = omitA({ a: 1, b: 2 });
而当你调用 时fpOmit('a')
,它的两个类型参数T
和都D
必须指定。但是,虽然编译器可以D
从'a'
输入推断,但它根本不知道推断什么T
,因此回退到约束:
const omitA = fpOmit('a');
// const fpOmit: <Record<string, unknown>, "a">(
// ...props: "a"[]) => (obj: Record<string, unknown>) =>
// { [x: string]: unknown; }
// const omitA: (obj: Record<string, unknown>) => { [x: string]: unknown; }
一旦发生这种情况,一切就结束了。的返回类型omitA()
不依赖于传入它的对象的类型;无论如何{ [x: string]: unknown; }
:
const fpResult = omitA({ a: 1, b: 2 });
// const fpResult2: { [x: string]: unknown; }
所以我们不能这样做。
相反,您需要做的是更改泛型类型参数的范围,以便仅在有足够信息可用时才需要指定它们。所以需要将参数移动到返回的T
函数的调用签名中。这也意味着你必须重新表述你的约束;您必须用以下方式表达,而不是相反:fpOmit()
T
D
declare const fpOmit: <D extends PropertyKey>(
...props: D[]) => <T extends Record<D, unknown>>(
obj: T) => { [K in Exclude<keyof T, D>]: T[K]; }
现在一切正常:
const fpResult = fpOmit('a')({ a: 1, b: 2 });
// const fpResult: { b: number; }
如果你像以前一样把它拆开,你会明白为什么:
const omitA = fpOmit('a');
// const fpOmit: <"a">(
// ...props: "a"[]) => <T>(obj: T) =>
// { [K in Exclude<keyof T, "a">]: T[K]; }
// const omitA: <T extends Record<"a", unknown>>(
// obj: T) => { [K in Exclude<keyof T, "a">]: T[K]; }
从 返回的函数fpResult()
在 的类型中仍然是通用T
的obj
,因此返回的类型omitA()
将取决于该类型:
const fpResult2 = omitA({ a: 1, b: 2 });
// const fpResult2: { b: number }
推荐阅读
- flutter - Flutter - 我想使用 GestureDetector 而不是按钮
- javascript - 如何创建表单格式的json数组vuejs?
- r - FUN(内容(x),...)中的错误:'utf8towcs'中的输入'XxX std chgs to send,'1.50 to rcv'无效
- sql-server - 尝试在 VS / SSDT 中连接 SQL Server 以编辑 DTSX / SSIS 进程,“无效的类字符串”
- reactjs - Redux Toolkit `useAppSelector` 值在未更新时会导致渲染
- javascript - 无效的位域标志或编号:37035584
- c# - 当 child.Parent 属性在循环内更改时,为什么 foreach over this.Controls 集合无法正常工作
- java - 从选择的数据中创建一个唯一的字符串(可反向工程)
- c - 通过使用 while 循环,C 程序未在整数计数 150 处退出的问题
- apache-spark - 如何在 pyspark groupby 上将 UDF 与 pandas 一起使用?