【问题标题】:Recursive index signature in TypeScriptTypeScript 中的递归索引签名
【发布时间】:2020-07-08 06:45:37
【问题描述】:

我正在尝试将索引签名类型添加到嵌套对象,以便我可以通过计算值访问它的一些属性。

例如,给定这个对象:

export const english = {
  home: {
    title: 'Title',
    characters_list: 'Characters list',
    moves: 'Moves',
    header: 'Home',
    search: 'Search',
  },
  character: {
    detail: {
      about: {
        title: 'About',
        info: 'Info',
        full_name: 'Full name',
        status: 'Status',
        gender: 'Gender',
      },
      origin: {
        title: 'Origin',
        origin: 'Origin',
        current_location: 'Current location',
      },
    },
  },
}

我想像这样访问它的属性:

const key = 'moves';
english.home[key]

但我做不到。到目前为止,我已经能够输入第一层

export type Translation = {
    [key: string]: typeof english[keyof typeof english]
}

我认为这需要某种递归方法,但我不知道如何实现它

【问题讨论】:

  • 物体形状的动态部分是什么?翻译中的对象结构是否相同?只要key 是一个常量,访问english.home[key] 就应该可以工作。
  • 它确实在运行时工作,但我收到 TypeScript 错误,指出“字符串”不是我的对象的索引。不同的语言应该具有相同的结构,是的。对象的形状和内容是恒定的,但在某些情况下我需要使用计算键而不是直接访问元素

标签: typescript typescript-typings


【解决方案1】:

好的,我想我知道我们需要做什么。问题是对象允许键是string 类型中的每个值,但是一旦定义了类型,就可以使用string 的子集。因此,TypeScript 会抛出一个错误——这是正确的——你不能只使用 every 字符串访问该错误,而只能使用有效键的字符串(例如movestitle 等)

因此,在某一时刻,您必须确保要访问的密钥有效。

以下是可能性

a) 字符串文字 w const context

你分配的那一刻

const key = 'moves'

key 不是string 类型,而是'moves' 类型,一个非常具体的值类型(或文字类型)和string 的子类型。这种访问应该有效

const key = 'moves'
english.home[key] // ?

或者,定义它们as const 以获得相同的效果

let key = 'moves' as const // also 'moves', not string

与通用拾取函数的行为相同

function pick<T extends Object, K extends keyof T>(o: T, k: K) {
  return o[k]
}
pick(english.home, 'moves')
pick(english, 'home')
pick(pick(english, 'home'), 'moves')

这与直接属性访问基本相同,但您让通用约束帮助您找到正确的类型。

b) 键入谓词以缩小范围

但是您使用的不是字符串文字,而是计算键。这意味着您必须以某种方式从大字符串集合到较小的实际键集合。你可以使用一个函数来检查这个键是否有效(无论如何你都应该这样做),以及一个类型谓词来缩小控制流中的类型(你很快就会看到它是什么)

想象一下这样的辅助函数:

function isKeyOf<O extends Object>(o: O, k: string | number | symbol): k is keyof O {
  return typeof k === "string" && Object.keys(o).indexOf(k) >= 0
}

这个函数有很多事情发生。

  1. 我们定义了一个名为 O 的泛型,它是一个 Object 来约束
  2. 我们允许传入一个键 k,它可以是所有可能的对象键类型
  3. 然后我们检查它是否是正确的字符串属性访问器以及它是否在键列表中。如果是这样,我们可以肯定地说 k 的类型是 keyof O

类型谓词适用于返回布尔值的函数。如果这个条件为真,那么类型就会变成不同的东西(即keyof O

您可以使用它来创建pickSafe 函数,您可以在其中传递任何键

function pickSafe<T extends Object>(o: T, k: string) {
  if (isKeyOf(o, k)) {
    return o[k]
  }
  throw new Error('Not accessible')
}


pickSafe(english.home, 'moves')
pickSafe(english, 'home')
pickSafe(pickSafe(english, 'home'), 'moves')

问题是,当此访问有效时,您会丢失有关正在访问的对象的信息。然后,您需要进行运行时类型检查。做

const res = pickSafe(english, 'home')

.. 将鼠标悬停在res 上,您会看到可以从中产生哪些可能的类型

这实际上是所需的行为,因为它会告诉您应用程序中的哪些地方可能会变得不清楚。

如果你真的知道你的应用可以正常工作并且计算出的键是什么,你总是可以做一个类型转换来覆盖 TypeScript 的不确定性

const keyey = myRandomString as keyof typeof english.home
pick(english.home, keyey)

这是fiddle around 的游乐场。

我希望这会有所帮助!

【讨论】:

    【解决方案2】:

    你可以使用简单的javascript函数reduce

    const key = 'home.moves';
    
    const value = key.split('.').reduce((obj,prop) => obj[prop], english);
    

    【讨论】:

    • 首先,谢谢!我想这也可以,(english.home as any)[key] 也可以,但我希望整个对象树都有 id 签名类型以避免这些变通方法
    猜你喜欢
    • 1970-01-01
    • 2019-05-19
    • 2023-03-13
    • 2020-02-21
    • 2019-04-06
    • 2018-07-24
    • 2020-01-27
    • 2016-10-11
    • 2020-02-02
    相关资源
    最近更新 更多