问题描述
我正在用Typescript构建Apollo GraphQL服务器,无法理解在类型系统中处理事物的正确方法。尽管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[]=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
}
}
}
解决方法
我的经验法则是尽可能使用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
。是“支出”还是“储蓄”类型?在这种情况下,我们还希望在type
和attributes
之间建立链接。
此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
}
}