typescript - TypeScript 中的递归索引签名

标签 typescript typescript-typings

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

例如,给定这个对象:

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]
}

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

最佳答案

好的,我想我知道我们需要做什么。问题在于对象允许键为 string 类型中的每个值,但是一旦定义了类型,您就可以使用 string 的子集。因此,TypeScript 会抛出一个错误(理所当然),您不能只使用每个字符串来访问,而只能使用作为有效键的字符串(例如 moves >标题等)

因此,在某个时间点,您必须确保要访问的 key 有效。

以下是可能性

a) 带有 const 上下文的字符串文字

你分配的那一刻

const key = 'moves'

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

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

或者,将它们定义为 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 的泛型,它通过成为对象来进行约束
  2. 我们允许传入一个键k,它可以是所有可能的对象键类型
  3. 然后我们检查它是否是正确的字符串属性访问器以及它是否在键列表中。如果是这样,我们可以有把握地说 k 是 keyof O
  4. 类型

类型谓词适用于返回 bool 值的函数。如果此条件为真,则类型将变为不同的内容(即 keyof O)

您可以使用它创建一个 pickSafe 函数,您可以在其中传递任何 key

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)

这是一个供你玩耍的 Playground fiddle around .

我希望这有帮助!

关于typescript - TypeScript 中的递归索引签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62777709/

相关文章:

typescript - Typescript 中的 Float64Array 和 Array<number> 有什么区别?

javascript - .d.ts 文件的构造和应用的简单示例?

typescript - 文字类型推断 - Typescript

typescript - 找不到第三方模块的声明文件 - 如何声明和解决这些错误

typescript - `TypeORM` 和 `Electron` 在尝试访问数据库时导致 "This option/function is not supported in the browser environment"错误

typescript - 类型 'toGMTString' 上不存在属性 'Date'

typescript - 是什么导致 Jest 在此代码中显示 75% 的分支覆盖率?

typescript - Vue 3 中的子组件未更新

javascript - TypeScript 编译器无法在 for of 循环中找到 immutable.js Map 迭代器

typescript 联合类型不会抛出错误