这篇笔记📒主要记录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>三个部分组成,
- 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; // 可选,外部文件的路径
}
- SFCTemplateBlock 接口
SFCTemplateBlock 继承自 SFCBlock,专门描述<template>块。它有一个固定的 type 属性值 'template',并且可能包含一个 ast 属性,表示模板的抽象语法树(AST)。
export interface SFCTemplateBlock extends SFCBlock {
type: 'template'; // 固定为 'template',表示模板块
ast?: RootNode; // 可选,模板的 AST(抽象语法树)
}
- 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; }'.
在这个示例中:
函数 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;否则,它保持原来的类型。