Как мне правильно объявить следующие интерфейсы / типы typescript?

#typescript #typescript2.0 #typescript-types

#typescript #typescript2.0 #typescript-типы

Вопрос:

Я создаю сервер Apollo GraphQL на Typescript и испытываю проблемы с пониманием правильного способа обработки данных в системе типов. Хотя GraphQL и Apollo являются частью кода, меня интересует конкретно часть TypeScript. У меня также возникают проблемы с пониманием роли интерфейсов по сравнению с типами и наилучшей практики для каждого (т. Е. Когда Вы используете тип по сравнению с интерфейсом, как вы справляетесь с расширением каждого и т. Д.).

Большая часть неясностей, которые у меня есть, находится в распознавателе. Я оставлю комментарии и вопросы, разбросанные по коду рядом с соответствующими частями, о которых я спрашиваю. Еще раз спасибо за любую помощь, которую вы можете предложить:

 
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[]=spendingamp;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
    }
  }
}
  

Комментарии:

1. Пожалуйста, разделите свой пост на несколько, по одному вопросу на сообщение.

2. @DanielRearden Я удалил все вопросы, которые не имеют отношения к типам или интерфейсам. Я оставляю остальные, поскольку они тесно связаны

Ответ №1:

Мое эмпирическое правило — использовать interfaces там, где это возможно. В принципе, вы можете использовать интерфейс всякий раз, когда имеете дело с объектом, у которого есть известные ключи (значения могут быть сложными типами). Таким образом, вы можете создать BankingAccount interface .

Способ настройки расходных и сберегательных счетов для расширения общего интерфейса великолепен!

Когда у вас есть a BankingAccount , вы знаете, что у него есть атрибуты расходов или сохранения, но вы не знаете, какие именно.

Один из вариантов — проверить, какой тип используется, используя защиту типа.

Другой вариант — определить дополнительный type , который обладает всеми свойствами обоих типов учетных записей, но необязателен.

 type CombinedAttributes = Partial<SpendingAccountAttributes> amp; Partial<SavingsAccountAttributes>
  

Что я лично хотел бы сделать, так это определить your BankingAccount таким образом, чтобы у него были полные атрибуты для расходования или сохранения, но атрибуты другого типа являются необязательными. Это означает, что вы можете получить доступ к этим свойствам без ошибок, но они могут быть undefined .

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

И после ввода всего этого…Я понял, что BankingAccount имеет type . Это тип «расходы» или «сбережения»? В этом случае мы также хотим установить связь между type и attributes .

Это BankingAccount определение типа должно позволять вам получать доступ к атрибутам любого типа, а также позволять вам сузить учетную запись как сберегательную, так и расходную, просто проверяя значение type . Из-за объединения здесь это должно быть a type , а не an interface , но это на самом деле не имеет значения.

 type BankingAccount = {
    id: string;
    attributes: CombinedAttributes;
} amp; ({
    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

Комментарии:

1. Это Partial было именно то, что я искал. Спасибо! Кроме того, тип для обоих из них BankingAccount должен отличаться от других типов (например, InvestmentAccount).