创建需要设置两个可能属性之一的接口时,类型联合很有帮助:
enum Plans { bronze, silver, gold }
type Plan = keyof typeof Plans;
interface CreateSubscriptionBase {
/** User's id */
uid: string;
/** Plan id */
planName: Plan;
}
interface CreateSubscriptionPaymentMethod extends CreateSubscriptionBase {
/** Payment Method id */
paymentMethod: string;
}
interface CreateSubscriptionSource extends CreateSubscriptionBase {
/** Source id */
source: string;
}
export type CreateSubscriptionParams =
CreateSubscriptionPaymentMethod
| CreateSubscriptionSource
const sParams = {
uid: 'foo',
planName: 'bronze',
source: 'source',
} as CreateSubscriptionParams
const pParams = {
uid: 'foo',
planName: 'bronze',
paymentMethod: 'paymentMethod',
} as CreateSubscriptionParams
但是,当使用该接口解构函数参数时,突然间事情变得糟糕:
function createSubscription({ uid, planName, paymentMethod, source }: CreateSubscriptionParams) {
return 'hi'
}
example.ts:36:46 - error TS2339: Property 'paymentMethod' does not exist on type 'CreateSubscriptionParams'.
36 { uid, planName, paymentMethod, source }: CreateSubscriptionParams,
~~~~~~~~~~~~~
example.ts:36:61 - error TS2339: Property 'source' does not exist on type 'CreateSubscriptionParams'.
36 { uid, planName, paymentMethod, source }: CreateSubscriptionParams,
~~~~~~
我们怎样才能做到这一点?
TypeScript 编译器无法以这种方式分解。当您使用类型并集时,它会尝试查找您看到的公共字段。因为联合意味着这个或这个对象。不是这个和这个对象。
也许,你应该重写你的接口。在本文中,您可以找到一个可以启发您的示例。
最终使用了一些带有 rest 参数的类型保护,以使可选参数模棱两可。
// let's imagine some app types
interface Customer extends Record<string, any> { }
interface Subscription extends Record<string, any> {}
enum Prices {
bronze = 30,
silver = 40,
gold = 100,
}
type Plan = keyof typeof Prices
interface UidParams {
uid: string;
}
interface PaymentMethodParams {
paymentMethod: { id: string }['id'];
}
interface SourceParams {
source: { id: string }['id'];
}
interface CreateSubscriptionBase {
planName: Plan;
coupon?: string;
}
type CreateSubscriptionParams =
CreateSubscriptionBase & UidParams & (PaymentMethodParams | SourceParams)
const isPaymentMethod = (params:
PaymentMethodParams
| SourceParams): params is PaymentMethodParams =>
!!(params as PaymentMethodParams).paymentMethod &&
!(params as SourceParams).source;
const catchAttachError =
(paymentTypeName: keyof PaymentMethodParams | keyof SourceParams) =>
(error: Error): never => {
console.error(error);
throw new Error(`Could not attach ${paymentTypeName} to customer`);
};
declare function attachPaymentMethod(params: UidParams & PaymentMethodParams): Promise<Customer>
declare function attachSource(params: UidParams & SourceParams): Promise<Customer>
async function attachPaymentType(
{ uid, ...paymentType }: UidParams & (PaymentMethodParams | SourceParams)
): Promise<Customer> {
return isPaymentMethod(paymentType)
? attachPaymentMethod({ uid, ...paymentType }).catch(catchAttachError('paymentMethod'))
: attachSource({ uid, ...paymentType }).catch(catchAttachError('source'));
}
async function createSubscription({ coupon, planName, uid, ...paymentType }: CreateSubscriptionParams): Promise<Subscription> {
const { id: customer } = await attachPaymentType({ uid, ...paymentType });
// ... yadda yadda ... imagine we did it
const subscription = {} as Subscription;
return subscription;
}