使用 TypeScript,如何定义返回特定类型的泛型、条件映射类型?

问题描述 投票:0回答:1

我想编写一组 TypeScript 类型和一个函数,它将任意深度的对象作为输入,其中叶节点的形状为

{ text: string, [key: string]: string }
并返回一个具有相同整体结构的对象,但叶子是普通的
 string
s.

例如

const input = {
  button: {
    continue: {
      text: "Continue",
      isTranslated: false
    },
    next: {
      text: "Proceed",
      context: "Button for next screen control"
    }
  },
  error: {
    text: "Unknown error",
    context: "Err message when specific error cause is not known"
  }
};

const output = {
  button: {
    continue: "Continue",
    next: "Proceed"
  },
  error: "Unknown error"
}

返回的类型必须具有对象的特定键,因为我想使用它们来索引它。

我的最终用例是使用转换后的对象的键来调用函数,例如

getTranslation('button.continue')
。我已经有一个类型可以将对象键转换为点表示法形式,所以这里忽略它。我需要的是弄清楚如何映射任意深度的图状结构并变换叶节点。

我尝试过以下方法:

 type Graphlike<Leaf> = {
  [key: string]: Graphlike<Leaf> | Leaf;
};

 type Flattened<T, Leaf, FlattenedValue> = {
  [K in keyof T]: T[K] extends Leaf ? FlattenedValue : T[K] extends T ? Flattened<T, Leaf, FlattenedValue> : never;
};


type TranslationLeaf = {
  text: string;
  [key: string]: unknown;
};

type TranslationMap = Graphlike<TranslationLeaf>;

type FlattenedTranslationMap = Flattened<TranslationMap, TranslationLeaf, string>;

function isTranslationLeaf(obj: TranslationLeaf | TranslationMap): obj is TranslationLeaf {
  return "text" in obj;
}

export function simplifyTranslations<T extends TranslationMap>(obj: T): FlattenedTranslationMap {
  const result = {} as FlattenedTranslationMap;

  for (const key in obj) {
    if (isTranslationLeaf(obj[key])) {
      const node = obj[key] as TranslationLeaf;
      // @ts-ignore
      result[key] = node.text;
    } else {
      const node = obj[key] as TranslationMap;
      // @ts-ignore
      result[key] = simplifyTranslations(node);
    }
  }

  return result;
}

const output = simplifyTranslations(input);

type ResultantType = typeof output;

但是,这会返回结果类型

{ [key: string]: never }
。值得注意的是,底层的实际 JS 按预期工作。我只需要键的类型安全。

typescript typescript-generics
1个回答
0
投票

你需要使用泛型类型,不能是静态类型。

您可以使用带有条件类型运算符的递归类型来仅转换与您的

Leaf
类型匹配的字段。

type FlattenedTranslationMap<T extends TranslationMap> = {
  [K in keyof T]: 
    // check if it's a Leaf structure
    // infer the text in case it is a constsnt and not just a generic string
    T[K] extends {text: infer Text extends string}
      // use inferred value
      ? Text 
      // check if it's a nested map
      : T[K] extends TranslationMap
        // recursively apply same transformations to it and it's children 
        ? Simplified<FlattenedTranslationMap<T[K]>>
        // if none of the abole leave untouched
        : T[K];
}


type Output = FlattenedTranslationMap<Input>
/*
type Output = {
    button: {
        continue: string;
        next: string;
    };
    error: string;
}
*/

游乐场

© www.soinside.com 2019 - 2024. All rights reserved.