TypeScript中类型不一致的数组

3

给定不一致类型的数组。这可以用于动态渲染HTML元素,例如。

interface IElement {
  type: 'button' | 'input'
  content: Button & Input
}

interface Button {
  text?: string;
  backgroundColor?: string;
}

interface Input {
  value?: string;
  placeholder?: string;
}

const elements: IElement[] = [
  {
    type: 'button',
    content: {
      text: 'Start',
      backgroundColor: 'salmon'
    }
  },
  {
    type: 'input',
    content: {
      value: 'phone',
      placeholder: 'Phone'
    }
  }
]

const newElement = elements.map(element => element.content.backgroundColor)

除了联合类型,还有没有其他方法可以根据 type 属性正确地进行类型转换?

3个回答

5
在TypeScript中,标准模式是使用所谓的“区分联合”(discriminated union)。基本上,在其他语言中你可能会使用IElement值,而在TypeScript中则尝试使用联合。这使得TypeScript能够在类型守卫中检查type字段的值时确定正确的类型。可能看起来像这样:
interface Button {
  type: 'button'
  text: string
  backgroundColor: string
}

interface Input {
  type: 'input'
  value: string
  placeholder: string
}

type ElementUnion = Button | Input

const elements: ElementUnion[] = [
  {
    type: 'button',
    text: 'Start',
    backgroundColor: 'salmon'
  },
  {
    type: 'input',
    value: 'phone',
    placeholder: 'Phone'
  }
]

function doSomething (el: ElementUnion) {
  if (el.type === 'button') {
    console.log(el.text) // TS knows this is Button
  } else if (el.type === 'input') {
    console.log(el.placeholder) // TS knows this is Input
  }
}

请注意,我没有将任何属性定义为可选或交叉类型,但是只要我先检查 type 字段,Typescript 仍然允许我在 doSomething 中使用它们。这就是区分联合类型的强大之处。
如果您希望,同时仍然可以使用继承模式:
type ElementType = 'button' | 'input'

interface IElement {
  type: ElementType
  content: unknown
}

interface Button extends IElement {
  type: 'button'
  content: {
    text: string
    backgroundColor: string
  }
}

interface Input extends IElement {
  type: 'input'
  content: {
    value: string
    placeholder: string
  }
}

type ElementUnion = Button | Input
    
function doSomething (el: ElementUnion) {
  if (el.type === 'button') {
    console.log(el.content.text) // TS knows this is Button
  } else if (el.type === 'input') {
    console.log(el.content.placeholder) // TS knows this is Input
  }
}

TypeScript Playground


为什么不使用泛型?这看起来很复杂且紧密耦合? - Sohan
@Sohan,你需要使用联合类型来正确定义content的类型。在你的通用答案中,你被迫定义content: T1 & T2,然后将所有实际内容属性设置为可选项。这不是类型安全的做法。 - ccarton
这是正确的,但基本接口IElement没有任何作用,可能会引起混淆。 - Aluan Haddad
1
@AluanHaddad,强制鉴别器类型为“ElementType”可能很有用。此外,可能会有依赖于这些对象的公共形状的代码,如果其中一个不匹配,则可能希望在定义点报告错误,而不是使用。 - ccarton
我同意它肯定会很有用。无论如何,我感谢你对其可选性的澄清。非常强的回答。 - Aluan Haddad

2
您可以使用type
type IElement = {
  type: 'button'
  content: Button
} | {
  type: 'input'
  content: Input
}

我不知道如何使用 interface


还没有尝试过这个,但这个语法看起来很简短明了,我喜欢它。希望它可以很好地与循环搭配。 - Daniel
很遗憾,这种方式在IDE中无法与循环一起使用。 - Daniel

-1

如果你问我要选择哪种类型,我会选择Typescript泛型

这里是一个示例,

interface IElement<T> {
    type: string,
    content: T
}

interface Button {
    text?: string;
    backgroundColor?: string;
}

interface Input {
    value?: string;
    placeholder?: string;
}

const elements: IElement<Button>[] = [
    {
        type: 'button',
        content: {
            text: 'Start',
            backgroundColor: 'salmon'
        }
    }
]
const newElement = elements.map(element => element.content.backgroundColor);

如果您正在寻找具有多个类和合并功能的扩展,

interface IElement<T1 , T2> {
    type: string,
    content: T1 & T2
}

interface Button {
    text?: string;
    backgroundColor?: string;
}

interface Input {
    value?: string;
    placeholder?: string;
}

const elements: IElement<Button,Input>[] = [
    {
        type: 'button',
        content: {
            text: 'Start',
            backgroundColor: 'salmon'
        }
    },
    {
        type: 'input',
        content: {
            value: 'phone',
            placeholder: 'Phone'
        }
    }
]
const newElement = elements.map(element => element.content.backgroundColor);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接