这里是demo
我正在尝试键入此功能
const reduceTransformNode = (cacheNode, [transformKey, transformValue]) => {
const { [transformKey]: node } = cacheNode;
const newCacheValue =
typeof transformValue === "function"
? transformValue(node)
: traverse(transformValue, node);
return {
...cacheNode,
[transformKey]: newCacheValue
};
};
我似乎无法解决它,因为traverse
和reduceTransformNode
之间似乎存在循环依赖关系
这是我发现的solution可行,但并不理想。
function reduceTransformNode<T extends { [key: string]: any }>(cacheNode: T, [transformKey, transformValue]: [string, any]): T {
const { [transformKey]: node } = cacheNode;
const newCacheValue =
typeof transformValue === "function"
? transformValue(node)
: traverse(transformValue, node);
return {
...cacheNode,
[transformKey]: newCacheValue
};
};
我不想在代码中包含any
。任何人都可以尝试这个技巧TypeScript问题
答案 0 :(得分:3)
我不是100%肯定我理解用例,但是我倾向于将事情归结为以下类型。首先,我们应该确定函数式转换方法的返回类型应该是什么; any
的限制不够。看来您想使用类似reduce
的函数,因此转换可能应该保留输入的类型:
type MappedTransform<T> = {
[K in keyof T]?: MappedTransform<T[K]> | ((params: T[K]) => T[K]); // <-- not any, right?
};
由于您使用的是Object.entries()
,因此最好定义一个Entries<T>
类型,该类型的值将得出对象类型T
的键值元组的并集。
type Entries<T> = { [K in keyof T]-?: [K, T[K]] }[keyof T];
在TypeScript中键入Object.entries()
仅返回类似[string, any]
的信息,因为TypeScript中的对象类型不是“精确的”(请参见microsoft/TypeScript#12946),并且总是具有比编译器所知道的更多的属性。 ; (有关更多信息,请参见this answer)。因此,如果您要告诉编译器Object.entries(transformObject)
仅具有类型为MappedTransform<R>
的已知条目,则需要使用type assertion:
function traverse<R>(cache: R, transformObject: MappedTransform<R>): R {
return (
Object.entries(transformObject) as Array<Entries<MappedTransform<R>>>
).reduce(reduceTransformNode, cache);
}
我们在上面声称traverse()
的类型为cache
的{{1}}并返回相同类型的值R
。 (从技术上讲,对R
的最外部调用而不是内部递归调用是正确的,因此我们必须在下面的traverse()
内使用另一个类型断言)。
为了使reduceTransformNode()
可以用作上述reduceTransformNode()
的回调,其类型必须是类型reduce()
的累加器的函数,类型为{{ 1}},然后返回类型为R
的新累加器:
Entries<MappedTransform<R>>
如果对于通用类R
的值类型为const reduceTransformNode = <R, K extends keyof R>(
cacheNode: R,
[transformKey, transformValue]: [K, MappedTransform<R[K]> | ((params: R[K]) => R[K]) | undefined]
): R => {
const { [transformKey]: node } = cacheNode;
// optional properties can technically be present but undefined, so let's deal with that
if (typeof transformValue === 'undefined') return cacheNode;
const newCacheValue =
typeof transformValue === "function"
? (transformValue as (params: R[K]) => R[K])(node)
: traverse(transformValue as R[K], node); // NOTE!
// ----------> ~~~~~~~~~~~~~~~~~~~~~~
// transformValue is a MappedTransform<R[K]> and not an R[K]. But
// traverse expects R[K] as its first parameter, so we need an assertion
// this is probably because in this recursive call to traverse, you are not
// just converting an initial R[K] to a final R[K]. It's possible that
// this type assertion is indicative of some problem, but it's hard for me
// to pinpoint. It's safer than any, at least
return {
...cacheNode,
[transformKey]: newCacheValue
};
};
而不是本质上等效的 [K, MappedTransform<R[K]> | ((params: R[K]) => R[K]) | undefined]
,则可以从编译器获得更好的类型安全保证。因此,可以将K extends keyof R
的实现视为对Entries<MappedTransform<R>>
的单个属性而不是对reduceTransformNode()
的所有属性的并集。
如您所见,在对R
的内部递归调用中,我们有一个类型断言。可能可以键入R
来为输出类型与输入类型匹配的最外层调用和内部类型(输出类型是输入类型的转换版本)内部调用工作……但我找不到一个在我花时间的时候。相反,我将使用带有解释性注释的类型断言(如上)继续进行。
让我们测试一下:
traverse()
看起来不错。 traverse()
的类型为const x = traverse(cache, {
a: {
b: {
c: (node) => node.filter((s) => !thingsToRemove.includes(s))
}
}
});
console.log(x.a.b.c.map(x => x.toLowerCase()).join(", ")); // topreserve1, topreserver2
,编译器将x
参数的 MyCache
回调类型为transformObject
(这很好)推导到a.b.c
同样,我可能错过了用例,并且您需要的东西与上面的类型不兼容。但希望它能为您提供一些指导。