TypeScript extends关键字用法(以电信诈骗和新农村等举例)

187 阅读8分钟

这篇笔记📒主要记录TypeScript extends的主要用途,在TypeScript 中,extends 是一个多功能的关键字,可以在不同的上下文中使用,以增强类型安全性、强制约束和提供灵活性。

extends 在 TypeScript 中的主要用途:

  • 扩展接口和类, 以构建现有类型的基础。
  • 限制泛型,确保它们被正确使用。
  • 创建条件类型,实现动态类型行为。
  • 在映射类型中强制约束,以更好地控制类型转换。

1. 扩展接口和类

extends 用于基于现有接口或类创建一个新的接口或类,从而继承其属性和方法。

  • 拓展接口 当接口 B 扩展了另一个接口 A 时,接口 B 会继承 A 的所有属性,并且可以添加新的属性或方法。。
  • 扩展类 当类 Child 扩展了另一个类 Parent 时,它会继承 Parent 的所有属性和方法,并且可以添加或重写属性或方法。
拓展接口

扩展接口例子1: 举一个电信诈骗例子,定义基础接口诈骗,然后各种诈骗类型extends基础诈骗,比如不久前我遇到的冒充微粒贷客服诈骗

// 定义一个基础接口,表示所有诈骗行为的共同特征
interface Scam {
 scamType: string; // 诈骗类型
 description: string; // 诈骗描述
 commonTargets: string[]; // 常见目标群体
}

// 扩展基础接口,定义冒充亲友诈骗
interface ImpersonationScam extends Scam {
 fakeRelation: string; // 冒充的关系(如朋友、家人)
 typicalScenario: string; // 典型场景描述
}

// 扩展基础接口,定义中奖诈骗
interface LotteryScam extends Scam {
 prize: string; // 伪造的奖品信息
 claimProcess: string; // 声称的领奖流程
}

// 扩展基础接口,定义虚假投资诈骗
interface InvestmentScam extends Scam {
 fakeInvestmentType: string; // 虚假投资类型(如股票、基金)
 promisedReturns: string; // 虚假承诺的收益率
}

// 扩展基础接口,定义冒充微粒贷客服诈骗
interface LoanCustomerServiceScam extends Scam {
 fakeLoanService: string; // 冒充的贷款服务
 falseClaims: string; // 诈骗者声称的问题或操作
 consequences: string; // 声称会产生的后果
}

// 创建不同诈骗类型的对象实例
const impersonationExample: ImpersonationScam = {
 scamType: '冒充亲友诈骗',
 description: '诈骗者冒充受害者的朋友或家人,声称有紧急情况需要资金。',
 commonTargets: ['老人', '年轻人'],
 fakeRelation: '朋友',
 typicalScenario: '诈骗者通过社交媒体联系受害者,声称手机被偷,急需借钱。',
};

const lotteryExample: LotteryScam = {
 scamType: '中奖诈骗',
 description: '诈骗者声称受害者中奖了,但需要先支付费用才能领取奖金。',
 commonTargets: ['所有年龄段的用户'],
 prize: '汽车大奖',
 claimProcess: '需先支付税费才能领奖。',
};

const investmentExample: InvestmentScam = {
 scamType: '虚假投资诈骗',
 description: '诈骗者提供虚假投资机会,承诺高回报。',
 commonTargets: ['有投资经验的成年人'],
 fakeInvestmentType: '虚拟货币',
 promisedReturns: '每月回报20%',
};

// 添加冒充微粒贷客服诈骗的示例
const loanCustomerServiceExample: LoanCustomerServiceScam = {
 scamType: '冒充微粒贷客服诈骗',
 description: '通过facetime, 骗子谎称其名下“微粒贷”贷款利率与国家相关政策不符合,利率过高,需注销账号或进行降息操作,否则会影响个人征信,产生严重后果。',
 commonTargets: ['iphone用户'],
 fakeLoanService: '微粒贷',
 falseClaims: '贷款利率过高,不符合国家政策,需注销账号或降息操作',
 consequences: '如果不操作,将影响个人征信,产生严重后果',
};

console.log(impersonationExample);
console.log(lotteryExample);
console.log(investmentExample);
console.log(loanCustomerServiceExample);

不同的诈骗种类都拓展了基础诈骗,比如冒充微粒贷的,增加了虚假信贷服务,虚假宣称,后果

扩展接口例子2: 看下Vue源码extends使用, 来自 parse 函数的定义,描述了单文件组件 (Single File Component, SFC) 不同部分的类型接口。

// packages/compiler-sfc/src/parse.ts

export interface SFCBlock {
  type: string
  content: string
  attrs: Record<string, string | true>
  loc: SourceLocation
  map?: RawSourceMap
  lang?: string
  src?: string
}

export interface SFCTemplateBlock extends SFCBlock {
  type: 'template'
  ast?: RootNode
}

export interface SFCScriptBlock extends SFCBlock {
  type: 'script'
  setup?: string | boolean
  bindings?: BindingMetadata
  imports?: Record<string, ImportBinding>
  scriptAst?: import('@babel/types').Statement[]
  scriptSetupAst?: import('@babel/types').Statement[]
  warnings?: string[]
  /**
   * Fully resolved dependency file paths (unix slashes) with imported types
   * used in macros, used for HMR cache busting in @vitejs/plugin-vue and
   * vue-loader.
   */
  deps?: string[]
}

export interface SFCStyleBlock extends SFCBlock {
  type: 'style'
  scoped?: boolean
  module?: string | boolean
}

代码解释: Vue的单文件组件SFC通常由<template>, <script>,和<style>三个部分组成,

  1. SFCBlock 接口
    SFCBlock 是一个通用接口,定义了 SFC 各个块的基本属性。所有的 SFC 块(如 template, script, style)都会继承这个接口。
export interface SFCBlock {
  type: string; // 块的类型,例如 'template', 'script', 'style' 等
  content: string; // 块中的内容,通常是代码或模板字符串
  attrs: Record<string, string | true>; // 块上的属性,例如 lang="ts" 或 scoped
  loc: SourceLocation; // 源码位置,用于调试或错误追踪
  map?: RawSourceMap; // 可选,源码映射信息,用于调试
  lang?: string; // 可选,指定语言类型,例如 'ts' 表示 TypeScript
  src?: string; // 可选,外部文件的路径
}
  1. SFCTemplateBlock 接口
    SFCTemplateBlock 继承自 SFCBlock,专门描述 <template>块。它有一个固定的 type 属性值 'template',并且可能包含一个 ast 属性,表示模板的抽象语法树(AST)。
  export interface SFCTemplateBlock extends SFCBlock {
    type: 'template'; // 固定为 'template',表示模板块
    ast?: RootNode; // 可选,模板的 AST(抽象语法树)
  }
  1. SFCScriptBlock 接口
    SFCScriptBlock 也是继承自 SFCBlock,用于描述 <script> 块。它有更多特定于脚本块的属性,例如 setup, bindings, imports, scriptAst, scriptSetupAst, warnings, 和 deps 等。
export interface SFCScriptBlock extends SFCBlock {
  type: 'script'; // 固定为 'script',表示脚本块
  setup?: string | boolean; // 可选,表示 `<script setup>` 的存在或其类型
  bindings?: BindingMetadata; // 可选,变量绑定的元数据
  imports?: Record<string, ImportBinding>; // 可选,导入的模块及其绑定信息
  scriptAst?: import('@babel/types').Statement[]; // 可选,解析后的脚本 AST
  scriptSetupAst?: import('@babel/types').Statement[]; // 可选,解析后的 `<script setup>` AST
  warnings?: string[]; // 可选,解析过程中的警告信息
  deps?: string[]; // 可选,用于宏中的导入类型依赖文件路径,用于热重载(HMR)缓存刷新
}

4. SFCStyleBlock 接口
SFCStyleBlock 同样继承自 SFCBlock,用于描述 <style> 块。它的属性包括 scoped 和 module,分别用于标记样式是否作用于局部范围以及是否启用 CSS Modules。

export interface SFCStyleBlock extends SFCBlock {
  type: 'style'; // 固定为 'style',表示样式块
  scoped?: boolean; // 可选,表示样式是否为 scoped(局部)
  module?: string | boolean; // 可选,启用 CSS Modules 或指定模块名称
}

总结

  • SFCBlock 是所有 SFC 部分的基础接口,定义了每个块的公共属性。
  • SFCTemplateBlock 针对 <template> 部分,可能包含模板的 AST。
  • SFCScriptBlock 针对 <script> 部分,支持脚本的解析、绑定和其他脚本相关特性。
  • SFCStyleBlock 针对 <style> 部分,支持样式作用域和 CSS Modules 的定义。

这些接口用于 Vue 内部,帮助解析和处理 .vue 文件中的各个部分,使得 SFC 的编译和运行时功能得以实现。

扩展类

扩展接口例子: 举一个新农村发展例子,定义农村发展接口,定义人口、位置、发展计划、优势,定义新农村发展接口,拓展方法: 使用新技术,发展乡村旅游,特色产业,

// 基础类:RuralDevelopment,表示农村发展的共同属性和方法
class RuralDevelopment {
  name: string; // 发展计划名称
  description: string; // 发展计划描述
  population: number; // 人口
  location: string; // 位置
  advantages: string[]; // 优势

  constructor(
    name: string,
    description: string,
    population: number,
    location: string,
    advantages: string[]
  ) {
    this.name = name;
    this.description = description;
    this.population = population;
    this.location = location;
    this.advantages = advantages;
  }

  develop(): void {
    console.log(`发展计划名称: ${this.name}`);
    console.log(`发展计划描述: ${this.description}`);
    console.log(`人口: ${this.population}`);
    console.log(`位置: ${this.location}`);
    console.log(`优势: ${this.advantages.join(", ")}`);
  }
}

// 子类:NewRuralDevelopment,表示新农村发展
class NewRuralDevelopment extends RuralDevelopment {
  newTechnologies: string[];
  tourismInitiatives: string[];
  specialtyIndustries: string[];

  constructor(
    name: string,
    description: string,
    population: number,
    location: string,
    advantages: string[],
    newTechnologies: string[],
    tourismInitiatives: string[],
    specialtyIndustries: string[]
  ) {
    super(name, description, population, location, advantages);
    this.newTechnologies = newTechnologies;
    this.tourismInitiatives = tourismInitiatives;
    this.specialtyIndustries = specialtyIndustries;
  }

  implementTechnologies(): void {
    console.log(`正在实施的新技术: ${this.newTechnologies.join(", ")}`);
  }

  promoteTourism(): void {
    console.log(`正在促进的乡村旅游项目: ${this.tourismInitiatives.join(", ")}`);
  }

  developSpecialtyIndustries(): void {
    console.log(`正在发展特色产业: ${this.specialtyIndustries.join(", ")}`);
  }
}

// 子类:RuralPovertyAlleviation,表示农村扶贫
class RuralPovertyAlleviation extends RuralDevelopment {
  supportPrograms: string[];

  constructor(
    name: string,
    description: string,
    population: number,
    location: string,
    advantages: string[],
    supportPrograms: string[]
  ) {
    super(name, description, population, location, advantages);
    this.supportPrograms = supportPrograms;
  }

  provideSupport(): void {
    console.log(`正在提供的扶贫支持项目: ${this.supportPrograms.join(", ")}`);
  }
}

// 使用示例

const newRural = new NewRuralDevelopment(
  "新农村发展计划",
  "提升农村生活质量和基础设施",
  5000, // 人口
  "东部地区",
  ["丰富的自然资源", "良好的气候"],
  ["智慧农业", "清洁能源"],
  ["生态旅游", "农家乐"],
  ["有机农产品", "手工艺品"]
);
newRural.develop();
newRural.implementTechnologies();
newRural.promoteTourism();
newRural.developSpecialtyIndustries();

const povertyAlleviation = new RuralPovertyAlleviation(
  "农村扶贫计划",
  "减少贫困人口,提升生活水平",
  3000, // 人口
  "西部山区",
  ["教育资源丰富", "政府支持政策"],
  ["教育援助", "医疗保障"]
);
povertyAlleviation.develop();
povertyAlleviation.provideSupport();

// 使用示例

const newRural = new NewRuralDevelopment(
  "新农村发展",
  "提升农村生活质量和基础设施",
  5000, // 人口
  "东部地区",
  ["丰富的自然资源", "良好的气候"],
  ["智慧农业", "清洁能源"],
  ["生态旅游", "农家乐"],
  ["有机农产品", "手工艺品"]
);
newRural.develop();
newRural.implementTechnologies();
newRural.promoteTourism();
newRural.developSpecialtyIndustries();

const povertyAlleviation = new RuralPovertyAlleviation(
  "农村扶贫",
  "减少贫困人口,提升生活水平",
  3000, // 人口
  "西部山区",
  ["教育资源丰富", "政府支持政策"],
  ["教育援助", "医疗保障"]
);
povertyAlleviation.develop();
povertyAlleviation.provideSupport();

2. extends用于泛型约束

extends 用于指定泛型类型必须是另一类型的子类型。这对于为泛型添加约束以确保类型安全性非常有用。

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength('Hello'); // 字符串有length属性
getLength([1, 2, 3]); // 数组有length属性
getLength(123); // Error: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

image.png

在这个示例中:

函数 getLength 接受一个泛型参数 T,该参数扩展了 { length: number },这意味着传递给该函数的任何类型都必须具有 length 属性。

3. extends用于条件类型

extends 还用于 TypeScript 的条件类型中,用于检查一个类型是否可赋值给另一个类型,并根据该检查返回不同的类型。

type IsString<T> = T extends string ? 'Yes' : 'No';

type A = IsString<string>; // 'Yes'
type B = IsString<number>; // 'No'

在这个例子中: IsString 是一个条件类型,用于检查 T 是否 extends string。 如果 T 是 string,它的结果为 'Yes';否则,它的结果为 'No'。

4.带有约束的映射类型

你可以在映射类型中使用 extends 来对映射类型的属性进行约束。

type Properties<T> = {
  [K in keyof T]: T[K] extends number ? string : T[K];
};

type Example = {
  a: number;
  b: boolean;
  c: string;
};

type Transformed = Properties<Example>;
// Transformed type will be:
// {
//   a: string; // number converted to string
//   b: boolean;
//   c: string;
// }

在这个例子中: Properties 是一个映射类型,它根据属性是否 extends number 来转换类型 T 的属性。 如果一个属性的类型是 number,它会被转换为 string;否则,它保持原来的类型。