使用Object.entries时保留类型 [英] Preserve Type when using Object.entries

查看:50
本文介绍了使用Object.entries时保留类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm fairly new to TypeScript, so I'm in the process of upgrading my old projects to utilize it.

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

However, I'm not sure how to preserve the correct Type when calling Object.entries on some data.

CodeSandbox示例

例如:

Level.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": [[]]
      }
    }
  }
}

在上述情况下,图块表示一个数组数组,每个数组都有两个数字.因此,[leftPos,topPos]都应键入数字.但是,在Level.tsx中,它们具有任何属性.我可以通过以下方法得到我想要的结果:

In the case of the above, tiles represents an Array of arrays, each with two numbers. Therefore, [leftPos, topPos] should both be typed as number. However, in Level.tsx, they have properties of any. I could get my desired result with the following:

  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}
        />
      ))

但是无论如何不应该推断出数字[]?

But shouldn't number[] be inferred anyways?

任何建议将不胜感激.

推荐答案

@jcalz的出色答案解释了为什么您尝试做的事情如此棘手.如果您想使基础架构和JSON保持相同,则他的方法可能会起作用.但我要指出的是,您可以通过仅以不同的方式构造数据来回避整个问题.我认为这将使您的开发人员体验以及您的数据是什么的阐明变得更好.

@jcalz's excellent answer explains why what you are trying to do is so tricky. His approach could work if you want to keep your underlying schemas and JSON the same. But I will point out that you can sidestep the entire problem just by structuring your data differently. I think that will make your developer experience, as well as the clarify of what your data is, better.

您遇到的一个基本问题是,您试图将 key:value 对映射视作某种无法通行的磁贴列表.但是使用 Object.entries 来获取无法逾越的图块类型在本质上是笨拙且令人困惑的.

One of the fundamental problems you're having is that you're trying to treat a map of key: value pairs as, in your case, some sort of list of impassable tiles. But it is inherently unwieldy and confusing to work with Object.entries just to get at your impassable tile types.

为什么不将 ImpassableTile 定义为类型,而将不可逾越的图块列表定义为该类型的数组?从概念上讲,这更好地匹配了数据实际表示的内容.它还完全避开了 Object.entries 及其困难,并使对数据的迭代更加简单明了.

Why not define ImpassableTile as a type, and the list of impassable tiles as an array of that type? That better matches, conceptually, what the data actually represents. It also sidesteps Object.entries and its difficulties entirely, and makes iterating over the data more simple and clear.

// levelData.ts
import levelJSON from "./levelJSON.json";

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

interface Level {
  tiles: Tiles;
}

export type UnpassableType = "rock" | "tree";

type UnpassableTile = {
  type: UnpassableType;
  position: number[];
};

interface Tiles {
  unpassable_tiles: UnpassableTile[];
}

export default levelJSON as ILevelJSON;

要正确匹配新接口,您还需要修改levelJSON.json.但是请注意,它更加干净,并且您不需要在level_2中为岩石或树木定义空数组,这些数组根本不存在:

To properly match the new interface, you'd need to modify levelJSON.json as well. But note that it's a lot cleaner and you'd don't need to define empty arrays for rocks or trees in level_2, those are simply absent:

{
  "level_1": {
    "tiles": {
      "unpassable_tiles": [
        { "type": "rock", "position": [0, 0] },
        { "type": "rock", "position": [2, 0] },
        { "type": "tree", "position": [2, 2] }
      ]
    }
  },
  "level_2": {
    "tiles": {
      "unpassable_tiles": []
    }
  }
}

现在,您可以非常轻松地在无法逾越的图块,它们的类型以及相关的位置数据上进行映射,同时保留完整的类型推断和安全性.而且,它看起来更清晰易懂.

Now you can very easily map over your impassable tiles, their types, and associated position data, all while retaining full type inference and safety. And it looks a lot more clear and understandable IMO.

// App.tsx
const UnpassableTileComponents = React.useMemo(() => {
  return levelData[`level_1`].tiles.unpassable_tiles.map(
    ({ type, position: [leftPos, topPos] }) => (
      <UnpassableTile
        key={`level_1_${type}_${leftPos}_${topPos}`}
        leftPos={leftPos}
        topPos={topPos}
        tileType={type}
      />
    )
  );
}, []);

https://codesandbox.io/s/goofy-snyder-u9x60?file =/src/App.tsx

您可以将此哲学进一步扩展到如何构造关卡及其界面.为什么不让 levelJSON 成为 Level 对象的数组,每个对象都有一个名称和一组图块?

You can further extend this philosophy to how you structure your Levels and their interfaces. Why not have levelJSON be an array of Level objects, each with a name and set of tiles?

interface Tiles {
  unpassable_tiles: UnpassableTile[];
}

interface Level {
  name: string;
  tiles: Tiles;
}

export type UnpassableType = "rock" | "tree";

type UnpassableTile = {
  type: UnpassableType;
  position: number[];
};

您的相应数据看起来会更干净:

Your corresponding data would look a lot cleaner:

[
  {
    "name": "level_1",
    "tiles": {
      "unpassable_tiles": [
        { "type": "rock", "position": [0, 0] },
        { "type": "rock", "position": [2, 0] },
        { "type": "tree", "position": [2, 2] }
      ]
    }
  },
  {
    "name": "level_2",
    "tiles": {
      "unpassable_tiles": []
    }
  }
]

对其进行迭代将变得更加清晰:

And iterating over it would become even more clear:

const level = levelData[0];

const UnpassableTileComponents = React.useMemo(() => {
  return level.tiles.unpassable_tiles.map(
    ({ type, position: [leftPos, topPos] }) => (
      <UnpassableTile
        key={`${level.name}_${type}_${leftPos}_${topPos}`}
        leftPos={leftPos}
        topPos={topPos}
        tileType={type}
      />
    )
  );
}, [level]);

https://codesandbox.io/s/hopeful-grass-dnohi?file =/src/App.tsx

这篇关于使用Object.entries时保留类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆