【问题标题】:How to add multiple assertions in Typescript如何在 Typescript 中添加多个断言
【发布时间】:2021-02-16 19:27:44
【问题描述】:

我正在尝试下面的代码,但不确定是否可以告诉 typescript 假设来自任一接口的类型

export interface CellA {
    text: string;
}

export interface CellB {
    date: Date;
}

var cell = {};

const { date, text } = cell as (CellA | CellB);

console.log(date, text);

TS 错误:“CellA | 类型”上不存在属性“日期” 单元格B'.(2339)

我所追求的是让打字稿假设被破坏的变量存在于任何一个接口中。 TS Playground Example

【问题讨论】:

  • 你需要一个类型保护,因为dateCellA 上不存在,但你可能正在尝试访问它。 text 上的 CellB 也是如此。
  • 你为什么要这样做?这闻起来像 x/y 问题...
  • @JaredSmith,所以我有一个 React 单元组件,它可以作为各种单元类型的宿主。每个时间都被单独定义并且可以有不同的道具
  • 那么你可能想要一个有区别的联合。我正在使用手机,这使得链接变得困难,但官方打字稿手册中有一个可靠的例子。
  • @NidhinJoseph 终于到了电脑前,添加了一个答案。希望它对你有用!

标签: javascript typescript assertion


【解决方案1】:

通常,如果您想在 Typescript 中根据参数/变量的类型切换控制流,您需要使用 discriminated union

interface CellA {
  text: string,
  kind: "CellA",
}

interface CellB {
  date: Date,
  kind: "CellB",
}

type Cell = CellA | CellB;

function takesACell(cell: Cell) {
  switch(cell.kind) {
    case "CellA":
      console.log("Got an A");
      cell.text; // no type error
      break;
    case "CellB":
      console.log("Got a B");
      cell.date; // no type error
      break;
  }
}

编译器将这种模式理解为意味着您基于运行时类型进行调度,但它仍将提供编译时类型安全,因为您不能只传递不符合要求的值。

Playground

【讨论】:

    【解决方案2】:

    您的问题是cell 可以是CellA 类型,它没有date 属性(因此您收到错误消息)。请注意,反之亦然,cell 也没有 text 属性。

    可能的解决方案

    1。使用交叉类型

    如果cell 应该是CellACellB 的组合,那么您可以创建CellA & CellB 类型的cell。例如:

    type CellC = CellA & CellB
    
    const cell: CellC = { date: new Date(), text: 'Hello!' }
    
    const { date, text }
    console.log(date, text)
    
    

    2。使用类型保护:Playground

    如果您需要在运行时键入检查值,则可以使用user-defined type guard。例如,如果您有一个变量cell,在运行时您不知道其类型:

    const cell = { text: 'Hello!' } // unknown type
    
    function isCellA(obj: any): obj is CellA {
        if(!obj) return false
        if(!obj.text) return false
        if(typeof obj.text !== 'string') return false
    
        return true
    }
    
    if(isCellA(cell)) {
        // at this point the compiler recognizes that cell
        // is of type CellA
        console.log(cell.text)
    }
    

    如果您需要在运行时进行类型检查,这可能是您的最佳选择。如果cell 确实不是您所期望的,它可以保持类型安全并有助于减少运行时错误。

    3。扩展另一个接口

    这与解决方案 #1 非常相似。如果CellACellB 应该共享一些属性,那么它们中的一个可以扩展另一个,或者它们可以扩展一个公共接口。例如:

    interface Cell {
        date: Date
    }
    
    interface CellC extends Cell {
        text: string
    }
    

    4:React 组件组成

    如果您在 React 中遇到此问题(如您的评论所述),您可以考虑使用一个单独的组件来返回每种单元格类型的主要组件。 示例:

    function ACell({ data }: { data: CellA } ) {
        return <Cell>{data.text}<Cell>
    }
    
    function BCell({ data }: { data: CellB } ) {
        return <Cell>{data.date.toString()}<Cell>
    }
    
    function Cell({ children }: { children: string }) {
        return <p>{children}</p>
    }
    

    也许这不符合您的特定用例,但类似的东西可能有助于分离组件之间每种类型单元的逻辑,并与第三个 Cell 组件共享通用逻辑。

    【讨论】:

    • 谢谢伙计,交叉路口的事情奏效了,我对这种方法很满意。
    • 很高兴听到! TypeScript 有时很难使用,但它是如此强大的语言。
    【解决方案3】:

    您这样做的方式是假设cell 同时具有datetext。但是,当您将 cell 指定为 CellACellB 类型时,打字稿会报错,因为每种类型都缺少其中一个属性。

    您可以只分配可选属性吗?像这样:

    interface CellType {
      date?: Date,
      text?: string
    }
    
    const { date, text } = cell as CellType
    

    或者如果你真的想强制 cell 严格地成为这些类型之一,我会在你定义变量时这样做:

    interface CellA {
      text: string;
    }
    
    interface CellB {
      date: Date;
    }
    
    type CellType = CellA | CellB
    
    const cell: CellType = { ... }
    

    【讨论】:

    【解决方案4】:

    如果可能,您可以扩展接口以接受任何其他值。例如:

    export interface CellA {
        text?: string;
        [index: string]: any;
    }
    
    export interface CellB {
        date?: Date;
        [index: string]: any;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-23
      • 2021-07-13
      • 2021-11-06
      • 2014-06-14
      • 2018-03-08
      • 2019-10-27
      • 1970-01-01
      相关资源
      最近更新 更多