TypeScript将对象从具有可选属性的类型转换为没有可选属性的类型

我是TypeScript的新手,现在面临一个非常琐碎的问题。我正在使用包装在我控制的服务功能中的第三方电子邮件库。第三方功能以及我的服务功能接受具有通用电子邮件配置属性(收件人,发件人,主题,正文等)的对象,但是第三方功能需要所有这些,而我的服务允许from属性是可选的。见下文。

// Third Party Code

type ThirdPartyEmailData = {
  from: string;
  to: string;
  subject: string;
  body: string;
}

const sendEmailWithThirdPartyService = (emailData: ThirdPartyEmailData) => {

  console.log('Sending email from:', emailData);

}

-----------------------------------------------------------------------------

// My Code

type MyEmailData = {
  from?: string;
  to: string;
  subject: string;
  body: string;
}

const sendEmail = (emailData: MyEmailData) => {

  emailData = { from: 'info@company.com', ...emailData };

  sendEmailWithThirdPartyService(emailData); // This does not compile

  /**
   * Argument of type 'MyEmailData' is not assignable to parameter of type 'ThirdPartyEmailData'.
   * Property 'from' is optional in type 'MyEmailData' but required in type 'ThirdPartyEmailData'.
   */

}

sendEmail({
  to: 'john.doe@customer.com',
  subject: 'I <3 TypeScript',
  body: 'But TypeScript hates me'
});

正如预期的那样,以上内容无法编译,因为类型'MyEmailData'无法分配给类型'ThirdPartyEmailData'的参数。我一直在寻找一种干净的方法来转换/更改/广播emailData的类型,然后再将其传递给第三方函数,但是我真的找不到一个好方法。以下是我想到的一些内容,但感觉不太好。

解决方案1:只是强制转换为第三方类型

// My Code

const sendEmail = (emailData: MyEmailData) => {

  // If you remove the line below the program still compiles but breaks at runtime
  emailData = { from: 'info@company.com', ...emailData };

  sendEmailWithThirdPartyService(<ThirdPartyEmailData>emailData); // This compiles

}

解决方案2:使用类型防护以确保存在from属性

// My Code

const sendEmail = (emailData: MyEmailData) => {

  emailData = { from: 'info@company.com', ...emailData };

  // By using the type guard, we ensure that the type of emailData overlaps ThirdPartyEmailData
  if (! hasFromProperty(emailData)) throw new Error('From property is missing');

  sendEmailWithThirdPartyService(emailData); // This compiles

}

-----------------------------------------------------------------------------

// Utilities

/** Utility to make certain keys of a type required */
type RequiredKeys<T, K extends keyof T> = Exclude<T, K> & Required<Pick<T, K>>

/** Typeguard for property 'from' in MyEmailData */
const hasFromProperty = (data: MyEmailData): data is RequiredKeys<MyEmailData, 'from'> => {

  return 'from' in data;

}

这看起来是一个非常普遍的问题,但我一直无法找到令人满意的解决方案。你能推荐什么?

评论