#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
}
}
Комментарии:
1. Это
Partial
было именно то, что я искал. Спасибо! Кроме того, тип для обоих из нихBankingAccount
должен отличаться от других типов (например, InvestmentAccount).