TypeScript:如何使用Object.entries

时间:2020-11-06 03:33:49

标签: typescript

这里是demo

我正在尝试键入此功能

const reduceTransformNode = (cacheNode, [transformKey, transformValue]) => {
  const { [transformKey]: node } = cacheNode;
  const newCacheValue =
    typeof transformValue === "function"
      ? transformValue(node)
      : traverse(transformValue, node);

  return {
    ...cacheNode,
    [transformKey]: newCacheValue
  };
};

我似乎无法解决它,因为traversereduceTransformNode之间似乎存在循环依赖关系

这是我发现的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问题

1 个答案:

答案 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


同样,我可能错过了用例,并且您需要的东西与上面的类型不兼容。但希望它能为您提供一些指导。

Playground link to code