javascript - 使用 Object.entries 时保留类型

标签 javascript reactjs typescript

我对 TypeScript 还很陌生,所以我正在升级我的旧项目以使用它。

但是,我不确定在对某些数据调用 Object.entries 时如何保留正确的类型。

CodeSandbox example

举个例子:

级别.tsx:

  const UnpassableTileComponents = useMemo(() => 
    Object.entries(levelData[`level_${gameLevel}`].tiles.unpassable_tiles).map(([tileType, tiles]) => (
      tiles.map(([leftPos, topPos], index) => (
        <UnpassableTile
          key={`${tileType}_${index}`}
          leftPos={leftPos * 40}
          topPos={topPos * 40}
          tileType={tileType}
        />
      ))
    )
  ).flat(), [gameLevel])

levelData.tsx:
import levelJSON from "./levelJSON.json";

interface ILevelJSON {
  [key: string]: Level;
}

interface Level {
  tiles: Tiles;
}

interface Tiles {
  unpassable_tiles: UnpassableTiles;
}

interface UnpassableTiles {
  rock: Array<number[]>;
  tree: Array<number[]>;
}

export default levelJSON as ILevelJSON;

levelJSON.json:
{
  "level_1": {
    "tiles": {
      "unpassable_tiles": {
        "rock": [[0, 0]],
        "tree": [[2, 0]]
      }
    }
  },
  "level_2": {
    "tiles": {
      "unpassable_tiles": {
        "rock": [[]],
        "tree": [[]]
      }
    }
  }
}

在上述情况下,tiles 表示一个数组的 Array,每个数组都有两个数字。
因此,[leftPos, topPos] 都应该输入为数字。但是,在 Level.tsx 中,它们具有任何属性。我可以通过以下方式获得我想要的结果:
  const UnpassableTileComponents = useMemo(() => 
    Object.entries(levelData[`level_${gameLevel}`].tiles.unpassable_tiles).map(([tileType, tiles]) => (
      tiles.map(([leftPos, topPos] : number[], index: number) => (
        <UnpassableTile
          key={`${tileType}_${index}`}
          leftPos={leftPos * 40}
          topPos={topPos * 40}
          tileType={tileType}
        />
      ))

但是无论如何不应该推断 number[] 吗?

任何意见,将不胜感激。

最佳答案

这与 Why doesn't Object.keys() return a keyof type in TypeScript? 等问题有关。 .两者的答案都是 TypeScript 中的对象类型不是 exact ;对象类型的值允许有编译器不知道的额外属性。这允许接口(interface)和类继承,这非常有用。但这可能会导致困惑。

例如,如果我有一个值 nameHaver类型 {name: string} ,我知道它有一个 name属性,但我不知道它只有一个 name属性(property)。所以我不能这么说Object.entries(nameHaver)将是 Array<["name", string]> :

interface NameHaver { name: string }
declare const nameHaver: NameHaver;
const entries: Array<["name", string]> = Object.entries(nameHaver); // error here: why?
entries.map(([k, v]) => v.toUpperCase()); 

如果 nameHaver不仅仅是一个 name属性,如:
interface NameHaver { name: string }
class Person implements NameHaver { constructor(public name: string, public age: number) { } }
const nameHaver: NameHaver = new Person("Alice", 35);
const entries: Array<["name", string]> = Object.entries(nameHaver); // error here: ohhh
entries.map(([k, v]) => v.toUpperCase());  // explodes at runtime!

哎呀。我们假设 nameHaver的值总是 string , 但一个是 number ,这对 toUpperCase() 不满意.唯一安全的假设是 Object.entries()产生是Array<[string, unknown]> (尽管标准库使用 Array<[string, any]> 代替)。

所以,我们能做些什么?好吧,如果你碰巧知道并且绝对确定一个值只有编译器知道的键,那么你可以为 Object.entries() 编写自己的类型。并改用它......你需要非常小心。这是一种可能的类型:
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T];
function ObjectEntries<T extends object>(t: T): Entries<T>[] {
  return Object.entries(t) as any;
}
as anytype assertion这抑制了关于 Object.entries() 的正常投诉.类型Entries<T>mapped type我们立即look up生成已知条目的联合:
const entries = ObjectEntries(nameHaver);
// const entries: ["name", string][]

这与我之前为 entries 手动编写的类型相同.如果您使用 ObjectEntries而不是 Object.entries在您的代码中,它应该“解决”您的问题。但请记住,您所依赖的事实是,您正在迭代其条目的对象没有未知的额外属性。如果有人添加了非 number[] 的额外属性输入 unpassable_tiles ,您可能在运行时遇到问题。

好的,希望有帮助;祝你好运!

Playground link to code

关于javascript - 使用 Object.entries 时保留类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62053739/

相关文章:

javascript - UDP 流到 webRTC

javascript - 如何使选择框下拉菜单变短

javascript - 使用按钮切换内容

javascript - 如何使用正则表达式在 javascript 的字符串中获取两个日期中的第二个?

reactjs - react不能在不刷新页面的情况下使用fileinput两次

javascript - 使用 es6 和 React 未定义函数

javascript - ReactJs - 列表组件内嵌套列表组件

angular - 如何在 nebular NBmenu 的侧边栏中折叠菜单项,当在外部单击或另一个菜单项时

typescript - 简单的 TypeScript 传播不起作用?

javascript - 期望服务器响应 promise 中的动态类型