如何在TypeScript中保存基于对象键的类型信息?

5hcedyr0  于 2023-05-01  发布在  TypeScript
关注(0)|答案(3)|浏览(123)

bounty还有6天到期。回答此问题可获得+250声望奖励。Evanss希望引起更多关注这个问题。

我有不同类型的项目(Item),它们可以存在于我的状态中的不同级别(Section.itemssubSection.items)。
我需要根据它们的类型(StrNum)将它们传递给不同的React组件。
下面的代码可以工作,但是它不是理想的数据结构。

type I_Str = {
  type: "str";
  value: string;
};
type I_Num = {
  type: "num";
  value: number;
};
type Item = I_Str | I_Num;

type SubSection = {
  name: string;
  items: Item[];
};

type Section = {
  name: string;
  items: Item[];
  subSection?: SubSection[];
};

type State = {
  section: Section[];
};

const state: State = {
  section: [
    {
      name: "Sec1",
      items: [
        { type: "str", value: "im a string" },
        { type: "num", value: 1 },
      ],
      subSection: [
        {
          name: "Sub1",
          items: [
            { type: "num", value: 999 },
            { type: "str", value: "sub section string" },
          ],
        },
      ],
    },
  ],
};

function Str({ item }: { item: I_Str }) {
  return <div>Value: {item.value.toUpperCase()}</div>;
}
function Num({ item }: { item: I_Num }) {
  return <div>Value: {item.value * 10}</div>;
}

function Items({ items }: { items: Item[] }) {
  return (
    <div style={{ border: "1px solid blue", padding: 20 }}>
      {items
        .sort((a, b) => {
          if (a.type > b.type) return 1;
          if (a.type < b.type) return -1;
          return 0;
        })
        .map((item) => {
          if (item.type === "str") return <Str item={item} />;
          if (item.type === "num") return <Num item={item} />;
        })}
    </div>
  );
}

function SubSection({ subSection }: { subSection: SubSection }) {
  return (
    <div>
      <div>{subSection.name}</div>
      <Items items={subSection.items} />
    </div>
  );
}

function Section({ section }: { section: Section }) {
  return (
    <div key={section.name} style={{ border: "1px solid grey", padding: 20 }}>
      <div>{section.name}</div>
      <Items items={section.items} />
      {section.subSection && section.subSection.length > 0
        ? section.subSection.map((sub) => {
            return <SubSection key={sub.name} subSection={sub} />;
          })
        : null}
    </div>
  );
}

export default function Page() {
  return (
    <div>
      {state.section.map((section) => {
        return <Section key={section.name} section={section} />;
      })}
    </div>
  );
}

它并不理想,因为items是一个数组,但顺序并不重要。
此外,你应该只能有一个项目的每一种类型,例如这不应该被允许:

items: [
  { type: "str", value: “String 1” },
  { type: "str", value: “String 2” },
]

我可以将Items更改为对象,然后我对数据结构感到满意,但TypeScript错误:

type Items = {
  str?: string;
  num?: number;
};

type SubSection = {
  name: string;
  items: Items;
};

type Section = {
  name: string;
  items: Items;
  subSection?: SubSection[];
};

type State = {
  section: Section[];
};

const state: State = {
  section: [
    {
      name: "Sec1",
      items: {
        str: "Im a st",
        num: 1,
      },
      subSection: [
        {
          name: "Sub1",
          items: {
            num: 999,
            str: "sub section string",
          },
        },
      ],
    },
  ],
};

function Str({ value }: { value: string }) {
  return <div>Value: {value.toUpperCase()}</div>;
}
function Num({ value }: { value: number }) {
  return <div>Value: {value * 10}</div>;
}

function Items({ items }: { items: Items }) {
  return (
    <div style={{ border: "1px solid blue", padding: 20 }}>
      {["num", "str"].map((key) => {
        const value = items[key];
        if (!value) return;
        if (key === "num") return <Num value={value} />;
        if (key === "str") return <Str value={value} />;
      })}
    </div>
  );
}

function SubSection({ subSection }: { subSection: SubSection }) {
  return (
    <div>
      <div>{subSection.name}</div>
      <Items items={subSection.items} />
    </div>
  );
}

function Section({ section }: { section: Section }) {
  return (
    <div key={section.name} style={{ border: "1px solid grey", padding: 20 }}>
      <div>{section.name}</div>
      <Items items={section.items} />
      {section.subSection && section.subSection.length > 0
        ? section.subSection.map((sub) => {
            return <SubSection key={sub.name} subSection={sub} />;
          })
        : null}
    </div>
  );
}

export default function Page() {
  return (
    <div>
      {state.section.map((section) => {
        return <Section key={section.name} section={section} />;
      })}
    </div>
  );
}

const value = items[key]; // Error occurs here
TS7053:元素隐式地具有“any”类型,因为类型“string”的表达式不能用于索引类型“Items”。 在类型“Items”上未找到参数类型为“string”的索引签名。
最好的方法是什么?我简化了我的示例代码,实际上我有更多的类型,而不仅仅是strnum和它们的值也可以更复杂(例如,变化的对象不仅仅是原始类型)。

cfh9epnr

cfh9epnr1#

我做的第一件事是重命名组件名(为ItemsComp),因为您对类型和组件使用了相同的名称。例如:-type Itemsfunction Items()
我的问题是你有一个动态的类型计数吗?我想不会,因为您已经为每种类型创建了单独的组件。例如:-function Str()function Num()
如果我是对的,你能做这样的事情吗?

function ItemsComp({ items }: { items: Items }) {
  return (
    <div style={{ border: "1px solid blue", padding: 20 }}>
      {items.str && <Str value={items.str} />}
      {items.num && <Num value={items.num} />}
      {/* other types */}
    </div>
  );
}

这次我不会出错

function ItemsComp({ items }: { items: Items }) {
  return (
    <div style={{ border: "1px solid blue", padding: 20 }}>
      {(["num", "str"] as const).map((key) => {
        const value = items[key];
        if (!value) return null;
        if (key === "num") return <Num key={key} value={Number(value)} />;
        if (key === "str") return <Str key={key} value={`${value}`} />;
        return null;
      })}
    </div>
  );
}
doinxwow

doinxwow2#

你需要将数组标记为as const,否则它就是一个string[]

;["num", "str"].map((key) => {
    key
    // ^?
    // (parameter) key: string
})

;(["num", "str"] as const).map((key) => {
    key
    // ^?
    // (parameter) key: "num" | "str"
})
vhmi4jdf

vhmi4jdf3#

最简单的方法是检查是否定义了每个属性:

function Items({ items }: { items: Items }) {
  return (
    <div style={{ border: "1px solid blue", padding: 20 }}>
      {items.str !== undefined && <Str value={items.str} />}
      {items.num !== undefined && <Num value={items.num} />}
    </div>
  );
}

注意:显式检查undefined很重要,以免在空字符串或零上短路。
Code Sandbox

相关问题