【问题标题】:How do I declare the following typescript interfaces/types correctly?如何正确声明以下打字稿接口/类型?
【发布时间】:2021-01-20 18:51:56
【问题描述】:

我正在使用 Typescript 构建 Apollo GraphQL 服务器,但无法理解在类型系统中处理事物的正确方法。尽管 GraphQL 和 Apollo 是代码的一部分,但我特别想知道 TypeScript 部分。我也很难理解接口与类型的作用以及每种类型的最佳实践是什么(即,您何时使用类型与接口,如何处理扩展它们等)。

我的大部分不明确之处在于解析器。我将在我询问的相关部分旁边的代码中散布 cmets 和问题。再次感谢您提供的任何帮助:


type BankingAccount = {
  id: string;
  type: string;
  attributes: SpendingAccountAttributes | SavingsAccountAttributes
}

// I've realized this "SpendingAccountAttributes | SavingsAccountAttributes" is the wrong way
// to do what I'm trying to do. I essentially want to the tell the 
// type system that this can be one or the other. As I understand it, the way it is written
// will create a Union, returning only the fields that are shared between both types, in this
// case what is essentially in the `BankingAttributes` type. Is that correct?

interface BankingAttributes = {
  routingNumber: string;
  accountNumber: string;
  balance: number;
  fundsAvailable: number;
}

// Is it better to remove the `SpendingAccountAttributes` and `SavingsAccountAttribute` specific
// types and just leave them as optional types on the `BankingAttributes`. I will
// in time be creating a resolver for the `SpendingAccount` and `SavingAccount` as standalone
// queries so it seems useful to have them. Not sure though


interface SpendingAccountAttributes extends BankingAttributes {
  defaultPaymentCardId: string;
  defaultPaymentCardLastFour: string;
  accountFeatures: Record<string, unknown>;
}

interface SavingsAccountAttributes extends BankingAttributes {
  interestRate: number;
  interestRateYTD: number;
}

// Mixing types and interfaces seems messy. Which one should it be? And if "type", how can I
// extend the "BankingAttributes" to "SpendingAccountAttributes" to tell the type system that
// those should be a part of the SpendingAccount's attributes?


export default {
  Query: {
    bankingAccounts: async(_source: string, _args: [], { dataSources}: Record<string, any>) : Promise<[BankingAccount]> => {
      // The following makes a restful API to an `accounts` api route where we pass in the type as an `includes`, i.e. `api/v2/accounts?types[]=spending&types[]=savings
      const accounts = await.dataSources.api.getAccounts(['spending', 'savings'])

      const response = accounts.data.map((acc: BankingAccount) => {
        const { fundsAvailable, accountFeatures, ...other } = acc.attributes

        return {
          id: acc.id,
          type: acc.type,
          balanceAvailableForWithdrawal: fundsAvailable,
          // accountFeatures fails the compilation with the following error:
          // "accountFeatures does not exist on type 'SpendingAccountAttributes | SavingsAccountAttributes'
          // What's the best way to handle this so that I can pull the accountFeatures
          // for the spending account (which is the only type of account this attribute will be present for)?
          accountFeatures,
          ...other
        }
      })

      return response
    }
  }
}

【问题讨论】:

  • 请将您的帖子分成多个帖子,每个帖子一个问题。
  • @DanielRearden 我已经删除了所有与类型或接口无关的问题。我将留下其余的,因为它们密切相关

标签: typescript typescript2.0 typescript-types


【解决方案1】:

我的经验法则是尽可能使用interfaces。基本上,只要您处理具有已知键的对象(值可以是复杂类型),您就可以使用接口。所以你可以把BankingAccount 变成interface

您设置支出和储蓄账户以扩展共享界面的方式非常棒!

当您拥有BankingAccount 时,您知道它具有支出或储蓄属性,但您不知道是哪个。

一种选择是使用type guard 来检查它是哪种类型。

另一种选择是定义一个额外的type,它具有两种帐户类型的所有属性,但可选。

type CombinedAttributes = Partial<SpendingAccountAttributes> & Partial<SavingsAccountAttributes>

我个人会做的是定义您的BankingAccount,使其必须具有用于消费或储蓄的完整属性,但其他类型的属性是可选的。这意味着您可以毫无错误地访问这些属性,但它们可能是 undefined

interface BankingAccount = {
  id: string;
  type: string;
  attributes: (SpendingAccountAttributes | SavingsAccountAttributes) & CombinedAttributes 
}

在输入所有这些内容之后...我意识到BankingAccount 有一个type。这种类型是“支出”还是“储蓄”?在这种情况下,我们还想在typeattributes 之间建立一个链接。

BankingAccount 类型定义应允许您访问任一类型的属性,同时还允许您仅通过检查type 的值来将帐户范围缩小为储蓄或支出。由于这里的联合,这必须是 type 而不是 interface,但这并不是真正重要的。

type BankingAccount = {
    id: string;
    attributes: CombinedAttributes;
} & ({
    type: "savings";
    attributes: SavingsAccountAtrributes;
} | {
    type: "spending";
    attributes: SpendingAccountAttributes;
}) 

function myFunc( account: BankingAccount ) {

    // interestRate might be undefined because we haven't narrowed the type
    const interestRate: number | undefined = account.attributes.interestRate;

    if ( account.type === "savings" ) {
        // interestRate is known to be a number on a savings account
        const rate: number = account.attributes.interestRate
    }
}

Typescript Playground Link

【讨论】:

  • Partial 正是我想要的。谢谢!此外,这两种类型的类型都是 BankingAccount,以区别于其他类型(如 InvestmentAccount)。
猜你喜欢
  • 2019-03-26
  • 1970-01-01
  • 2019-04-08
  • 2019-05-28
  • 2021-02-06
  • 2019-02-16
  • 1970-01-01
  • 2019-11-12
  • 2022-11-02
相关资源
最近更新 更多