VSCode 源码解密:一个"无用"属性背后的精妙设计 ------ 深入理解 readonly _serviceBrand: undefined
的设计哲学
引言
如果你阅读过 VSCode 的源代码,你可能会注意到一个奇怪的模式:在许多服务接口和实现类中,都声明了一个看似没有任何用处的属性:
typescript
readonly _serviceBrand: undefined;
或者使用 declare
关键字:
typescript
declare readonly _serviceBrand: undefined;
这个属性在运行时没有任何值(undefined
),也不会被真正使用。那么,为什么 VSCode 的开发者要在成百上千个类中添加这样一个"无用"的属性呢?本文将深入探讨这个设计模式背后的原理和必要性。
背景:TypeScript 的结构化类型系统
要理解 _serviceBrand
的作用,我们首先需要了解 TypeScript 的类型系统特性。
TypeScript 使用结构化类型(Structural Typing)
与 Java、C# 等语言使用的名义类型(Nominal Typing)不同,TypeScript 采用的是结构化类型系统。这意味着:
- 名义类型系统:类型的兼容性基于类型的名称。即使两个类型的结构完全相同,只要名字不同,它们就是不兼容的。
- 结构化类型系统:类型的兼容性基于类型的结构。只要两个类型的结构相同,它们就被认为是兼容的,无论它们的名字是什么。
让我们通过一个简单的例子来理解这个概念:
typescript
interface IServiceA {
name: string;
version: number;
}
interface IServiceB {
name: string;
version: number;
}
class ServiceA implements IServiceA {
name = 'ServiceA';
version = 1;
}
// 这在 TypeScript 中是完全合法的!
const serviceB: IServiceB = new ServiceA();
在上面的例子中,尽管 ServiceA
只实现了 IServiceA
接口,但由于 IServiceB
的结构与 IServiceA
完全相同,TypeScript 认为 ServiceA
的实例也可以赋值给 IServiceB
类型的变量。
结构化类型在大型项目中的问题
在小型项目中,结构化类型系统通常运作良好。但在像 VSCode 这样的大型项目中,会出现一些问题:
- 意外的类型兼容:两个完全不相关的服务,如果恰好有相同的属性结构,就可能被错误地互换使用。
- 依赖注入的歧义:在依赖注入系统中,如果两个服务接口的结构相同,类型系统无法区分它们。
- 重构风险:当你修改一个服务的接口时,可能会无意中影响到其他结构相似的服务。
VSCode 的依赖注入系统
VSCode 使用了一个强大的依赖注入(Dependency Injection, DI)系统来管理各种服务。让我们看看它是如何工作的。
服务定义
在 VSCode 中,服务通常按照以下模式定义:
typescript
// 1. 创建服务标识符
export const ILogService = createDecorator<ILogService>('logService');
// 2. 定义服务接口
export interface ILogService {
readonly _serviceBrand: undefined;
info(message: string): void;
error(message: string): void;
warn(message: string): void;
}
// 3. 实现服务
export class ConsoleLogService implements ILogService {
declare readonly _serviceBrand: undefined;
info(message: string): void {
console.log('[INFO]', message);
}
error(message: string): void {
console.error('[ERROR]', message);
}
warn(message: string): void {
console.warn('[WARN]', message);
}
}
依赖注入的使用
服务可以通过构造函数注入到其他类中:
typescript
export class FileService {
constructor(
@ILogService private readonly logService: ILogService
) {
this.logService.info('FileService initialized');
}
readFile(path: string): string {
this.logService.info(`Reading file: ${path}`);
// ... 读取文件的逻辑
return content;
}
}
为什么需要 _serviceBrand
?
让我们通过一个具体的场景来说明问题。假设我们有两个服务:
typescript
// 没有 _serviceBrand 的情况
export interface IConfigService {
getValue(key: string): any;
setValue(key: string, value: any): void;
}
export interface IStorageService {
getValue(key: string): any;
setValue(key: string, value: any): void;
}
这两个接口的结构完全相同!在 TypeScript 的结构化类型系统中,它们是完全兼容的。这意味着:
typescript
class MyComponent {
constructor(
@IConfigService private readonly configService: IConfigService
) {}
}
// 在注册服务时,可能会发生这种错误:
const storageService = new StorageService();
// 由于结构相同,TypeScript 不会报错!
serviceCollection.set(IConfigService, storageService);
这种错误在编译时不会被发现,只会在运行时导致难以调试的问题。
_serviceBrand
的工作原理
类型品牌化(Type Branding)
_serviceBrand
实现了一种称为"类型品牌化"(Type Branding)的技术。这是一种在结构化类型系统中模拟名义类型的方法。
在 VSCode 的依赖注入系统中,有一个关键的类型定义:
typescript
export type BrandedService = { _serviceBrand: undefined };
每个服务接口都必须包含这个 _serviceBrand
属性,使其成为一个"品牌化"的服务。
独特的类型标识
虽然所有服务的 _serviceBrand
都是 undefined
,但在 TypeScript 的类型系统中,每个接口的 _serviceBrand
都被视为独特的。这是因为 TypeScript 会根据接口的声明位置来区分它们。
让我们看一个完整的例子:
typescript
// 定义两个服务
export interface IConfigService {
readonly _serviceBrand: undefined;
getValue(key: string): any;
setValue(key: string, value: any): void;
}
export interface IStorageService {
readonly _serviceBrand: undefined;
getValue(key: string): any;
setValue(key: string, value: any): void;
}
// 现在,即使它们的其他结构相同,TypeScript 也会认为它们是不同的类型!
const config: IConfigService = ...;
const storage: IStorageService = config; // ❌ 编译错误!
declare
关键字的作用
在实现类中,我们通常使用 declare
关键字:
typescript
export class ConsoleLogService implements ILogService {
declare readonly _serviceBrand: undefined;
// ...
}
declare
告诉 TypeScript:"这个属性存在于类型系统中,但不要在 JavaScript 中生成任何代码"。这意味着:
- 类型检查时 :
_serviceBrand
被视为类的一部分,确保类型兼容性。 - 运行时 :不会为
_serviceBrand
分配任何内存或生成任何代码,完全零开销。
编译后的 JavaScript 代码中不会包含 _serviceBrand
:
javascript
// 编译后的 JavaScript
export class ConsoleLogService {
// _serviceBrand 不存在!
info(message) {
console.log('[INFO]', message);
}
}
深入分析:多角度理解 _serviceBrand
1. 类型安全的守护者
_serviceBrand
是类型安全的第一道防线。它确保:
- 正确的服务注入:依赖注入容器不会错误地注入结构相似但语义不同的服务。
- 编译时错误检测:类型错误在编译时就会被发现,而不是在运行时。
- 重构的安全性:当你修改一个服务接口时,所有使用该服务的地方都会得到类型检查。
2. 依赖注入系统的基石
在 VSCode 的依赖注入系统中,ServiceIdentifier
是用来标识服务的:
typescript
export interface ServiceIdentifier<T> {
(...args: any[]): void;
type: T;
}
export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
const id = <any>function (target: Function, key: string, index: number) {
if (arguments.length !== 3) {
throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
}
storeServiceDependency(id, target, index);
};
id.toString = () => serviceId;
return id;
}
ServiceIdentifier
与类型系统紧密集成,而 _serviceBrand
确保了每个服务的类型唯一性:
typescript
export type BrandedService = { _serviceBrand: undefined };
export interface IConstructorSignature<T, Args extends any[] = []> {
new <Services extends BrandedService[]>(...args: [...Args, ...Services]): T;
}
这个类型约束确保了只有带有 _serviceBrand
的服务才能被依赖注入系统识别和注入。
3. 防止意外的接口实现
考虑以下场景:
typescript
// 某个内部使用的简单对象
const simpleLogger = {
info(msg: string) { console.log(msg); },
error(msg: string) { console.error(msg); },
warn(msg: string) { console.warn(msg); }
};
// 如果没有 _serviceBrand
const logService: ILogService = simpleLogger; // ✅ 在没有 _serviceBrand 时可以通过
// 有了 _serviceBrand
const logService: ILogService = simpleLogger; // ❌ 编译错误!缺少 _serviceBrand
_serviceBrand
确保只有显式实现接口的类才能被当作服务使用,防止了意外的对象被误用为服务。
4. 代码文档化和可读性
_serviceBrand
还起到了文档化的作用:
- 当你看到一个接口有
readonly _serviceBrand: undefined
,你立即知道这是一个服务接口。 - 它明确表示这个接口应该通过依赖注入系统使用,而不是直接实例化。
- 它是 VSCode 架构约定的一部分,帮助开发者理解代码的设计意图。
5. 性能优化:零运行时开销
使用 declare readonly _serviceBrand: undefined
的一个巧妙之处在于它完全是编译时的特性:
- 编译时:提供强类型检查和类型唯一性保证。
- 运行时:不产生任何代码,不占用任何内存,完全零开销。
这是一个完美的"免费的抽象"(Zero-Cost Abstraction)示例。
6. 测试和 Mock 的便利性
在测试中,_serviceBrand
也很有用:
typescript
// 测试中的 Mock 服务
export class TestConfigurationService implements IConfigurationService {
declare readonly _serviceBrand: undefined;
private configuration: any = {};
getValue(key: string): any {
return this.configuration[key];
}
setValue(key: string, value: any): void {
this.configuration[key] = value;
}
}
Mock 服务必须显式声明 _serviceBrand
,这确保了 Mock 服务与真实服务具有相同的类型特征,避免了测试中的类型不一致问题。
实际应用示例:VSCode 源码中的真实案例
让我们看一些 VSCode 源码中的真实例子。
示例 1:日志服务(ILogService)
在 src/vs/platform/log/common/log.ts
中:
typescript
export const ILogService = createDecorator<ILogService>('logService');
export interface ILogService extends ILogger {
readonly _serviceBrand: undefined;
}
export interface ILogger {
trace(message: string, ...args: any[]): void;
debug(message: string, ...args: any[]): void;
info(message: string, ...args: any[]): void;
warn(message: string, ...args: any[]): void;
error(message: string | Error, ...args: any[]): void;
}
示例 2:状态服务(IStateService)
在 src/vs/platform/state/node/stateService.ts
中:
typescript
export class StateReadonlyService extends Disposable implements IStateReadService {
declare readonly _serviceBrand: undefined;
protected readonly fileStorage: FileStorage;
constructor(
saveStrategy: SaveStrategy,
@IEnvironmentService environmentService: IEnvironmentService,
@ILogService logService: ILogService,
@IFileService fileService: IFileService
) {
super();
this.fileStorage = this._register(
new FileStorage(environmentService.stateResource, saveStrategy, logService, fileService)
);
}
getItem<T>(key: string, defaultValue?: T): T | undefined {
return this.fileStorage.getItem(key, defaultValue);
}
}
export class StateService extends StateReadonlyService implements IStateService {
declare readonly _serviceBrand: undefined;
setItem(key: string, data?: object | string | number | boolean | undefined | null): void {
this.fileStorage.setItem(key, data);
}
removeItem(key: string): void {
this.fileStorage.removeItem(key);
}
}
示例 3:实例化服务本身
在 src/vs/platform/instantiation/common/instantiationService.ts
中:
typescript
export class InstantiationService implements IInstantiationService {
declare readonly _serviceBrand: undefined;
readonly _globalGraph?: Graph<string>;
private _globalGraphImplicitDependency?: string;
private _isDisposed = false;
private readonly _servicesToMaybeDispose = new Set<any>();
private readonly _children = new Set<InstantiationService>();
constructor(
private readonly _services: ServiceCollection = new ServiceCollection(),
private readonly _strict: boolean = false,
private readonly _parent?: InstantiationService,
private readonly _enableTracing: boolean = _enableAllTracing
) {
this._services.set(IInstantiationService, this);
this._globalGraph = _enableTracing ? _parent?._globalGraph ?? new Graph(e => e) : undefined;
}
protected _getOrCreateServiceInstance<T>(id: ServiceIdentifier<T>, _trace: Trace): T {
if (this._globalGraph && this._globalGraphImplicitDependency) {
this._globalGraph.insertEdge(this._globalGraphImplicitDependency, String(id));
}
const thing = this._getServiceInstanceOrDescriptor(id);
if (thing instanceof SyncDescriptor) {
return this._safeCreateAndCacheServiceInstance(id, thing, _trace.branch(id, true));
} else {
_trace.branch(id, false);
return thing;
}
}
}
示例 4:测试中的 Mock 服务
在 src/vs/platform/configuration/test/common/testConfigurationService.ts
中:
typescript
export class TestConfigurationService implements IConfigurationService {
_serviceBrand: undefined; // 注意:这里没有使用 declare
private configuration: any;
readonly onDidChangeConfigurationEmitter = new Emitter<IConfigurationChangeEvent>();
readonly onDidChangeConfiguration = this.onDidChangeConfigurationEmitter.event;
constructor(configuration?: any) {
this.configuration = configuration || Object.create(null);
}
getValue<T>(section?: string, overrides?: any): T {
return this.configuration[section || ''] as T;
}
updateValue(key: string, value: any): Promise<void> {
this.configuration[key] = value;
return Promise.resolve();
}
}
这些示例展示了 _serviceBrand
在 VSCode 源码中的广泛应用和一致性。
类型品牌化的技术细节
TypeScript 如何区分相同结构的接口?
你可能会问:如果所有的 _serviceBrand
都是 undefined
,TypeScript 怎么能区分它们呢?
答案在于 TypeScript 的类型系统如何处理属性。当你在不同的接口中声明相同名称和类型的属性时,TypeScript 会根据声明的上下文来追踪这些属性的"来源"。
虽然从运行时的角度来看,所有的 _serviceBrand
都是 undefined
,但在类型系统的静态分析阶段,TypeScript 会记住每个 _serviceBrand
属于哪个接口声明。
为什么使用 undefined
而不是其他类型?
VSCode 选择 undefined
有几个原因:
- 语义明确 :
undefined
表示"这个属性不应该有实际值"。 - 最小化类型声明:不需要定义特殊的类型或符号。
- 与
declare
配合 :declare readonly _serviceBrand: undefined
清楚地表达"这个属性仅用于类型检查"。 - 简洁性 :
undefined
是 TypeScript 的内置类型,不需要额外的导入或定义。
也有项目使用其他方式,如:
typescript
// 使用 never 类型
readonly _serviceBrand: never;
// 使用唯一符号
declare readonly _serviceBrand: unique symbol;
// 使用字符串字面量
readonly _serviceBrand: 'ILogService';
但 undefined
是最简洁和直观的选择,特别是与 declare
关键字结合使用时。
readonly
的重要性
使用 readonly
修饰符有几个重要原因:
- 防止修改 :确保
_serviceBrand
不能在运行时被修改(尽管使用declare
时它根本不存在)。 - 表达意图:明确表示这是一个类型标记,不是一个可变的属性。
- 类型兼容性:在 TypeScript 中,readonly 属性的类型兼容性检查更严格。
类似的设计模式
_serviceBrand
并不是 VSCode 独有的创新。这种类型品牌化模式在 TypeScript 社区中有多种变体。
1. Branded Types(品牌类型)
用于区分相同底层类型但语义不同的值:
typescript
type UserId = string & { readonly __brand: 'UserId' };
type ProductId = string & { readonly __brand: 'ProductId' };
function createUserId(id: string): UserId {
return id as UserId;
}
function createProductId(id: string): ProductId {
return id as ProductId;
}
function getUser(id: UserId) {
// 只接受 UserId,不接受普通 string 或 ProductId
console.log('Fetching user:', id);
}
const userId = createUserId('user-123');
const productId = createProductId('product-456');
getUser(userId); // ✅ 正确
getUser(productId); // ❌ 类型错误
getUser('user-789'); // ❌ 类型错误
2. Opaque Types(不透明类型)
用于创建类型安全的单位或度量:
typescript
declare const opaqueSymbol: unique symbol;
type Opaque<T, K> = T & { readonly [opaqueSymbol]: K };
type Meters = Opaque<number, 'meters'>;
type Feet = Opaque<number, 'feet'>;
type Kilograms = Opaque<number, 'kilograms'>;
function meters(value: number): Meters {
return value as Meters;
}
function feet(value: number): Feet {
return value as Feet;
}
function addMeters(a: Meters, b: Meters): Meters {
return (a + b) as Meters;
}
const distance1 = meters(100);
const distance2 = meters(50);
const height = feet(30);
addMeters(distance1, distance2); // ✅ 正确
addMeters(distance1, height); // ❌ 类型错误:不能混合 Meters 和 Feet
3. Phantom Types(幽灵类型)
用于编码类型状态或验证状态:
typescript
interface Validated<T> {
readonly value: T;
readonly _phantom: 'validated';
}
interface Unvalidated<T> {
readonly value: T;
}
function validateEmail(email: string): Validated<string> | null {
if (email.includes('@')) {
return { value: email, _phantom: 'validated' };
}
return null;
}
function sendEmail(email: Validated<string>) {
// 只接受经过验证的邮箱
console.log('Sending email to:', email.value);
}
const rawEmail = { value: 'test@example.com' };
const validatedEmail = validateEmail('test@example.com');
// sendEmail(rawEmail); // ❌ 类型错误:未验证
if (validatedEmail) {
sendEmail(validatedEmail); // ✅ 正确:已验证
}
VSCode 的 _serviceBrand
是这些模式在依赖注入场景中的具体应用,专门用于确保服务接口的类型唯一性。
常见问题解答
Q1: 为什么不使用 class 而使用 interface?
在 TypeScript 中,class 同时定义了类型和运行时的构造函数,而 interface 只定义类型。使用 interface 作为服务契约有几个优势:
- 更轻量:interface 在编译后完全消失,不产生任何 JavaScript 代码。
- 更灵活:一个 class 可以实现多个 interface,但只能继承一个 class。
- 更好的解耦:接口定义与实现分离,便于测试和替换实现。
- 依赖倒置原则:依赖于抽象(interface)而不是具体实现(class)。
Q2: declare
和直接声明有什么区别?
typescript
// 使用 declare(推荐)
class ServiceA implements IServiceA {
declare readonly _serviceBrand: undefined;
}
// 直接声明
class ServiceB implements IServiceB {
readonly _serviceBrand: undefined;
}
// 带初始化
class ServiceC implements IServiceC {
readonly _serviceBrand: undefined = undefined;
}
区别:
- 使用
declare
:告诉 TypeScript 这个属性仅用于类型检查,不会生成任何 JavaScript 代码。 - 直接声明(无初始化):TypeScript 会报错,要求初始化或在构造函数中赋值。
- 带初始化 :会在编译后的 JavaScript 中生成代码来初始化这个属性(尽管值是
undefined
)。
对于 _serviceBrand
,由于它仅用于类型检查,使用 declare
是最佳实践,可以避免不必要的运行时开销。
Q3: 可以不使用 _serviceBrand
吗?
技术上可以,但不推荐。如果不使用 _serviceBrand
:
- 失去类型唯一性保证:结构相似的服务可能被错误地互换。
- 可能导致服务注入错误:依赖注入系统无法在编译时检测类型错误。
- 代码意图不够明确:无法一眼识别哪些接口是服务接口。
- 重构风险增加:修改一个接口可能无意中影响其他接口。
- 无法利用类型系统的保护:失去了 TypeScript 提供的额外类型安全保障。
Q4: _serviceBrand
会影响性能吗?
完全不会 。由于使用了 declare
关键字,_serviceBrand
在运行时不存在:
- 编译时:用于类型检查和类型推导。
- 运行时:不占用内存,不生成代码,不影响性能。
- 零开销抽象:这是一个纯编译时特性,运行时完全透明。
你可以通过 TypeScript Playground 验证:声明了 declare readonly _serviceBrand: undefined
的类,编译后的 JavaScript 中不会包含这个属性。
Q5: 为什么要用 readonly
?
使用 readonly
有几个重要原因:
- 防止意外修改 :即使在类型检查层面,也确保
_serviceBrand
不会被修改。 - 表达设计意图:明确表示这是一个不可变的类型标记。
- 更严格的类型检查:TypeScript 对 readonly 属性有更严格的兼容性检查。
- 文档作用:告诉其他开发者这个属性的用途和约束。
Q6: 接口和实现类都要声明 _serviceBrand
吗?
是的:
- 接口中 :使用
readonly _serviceBrand: undefined;
定义契约。 - 实现类中 :使用
declare readonly _serviceBrand: undefined;
满足契约。
如果实现类不声明 _serviceBrand
,TypeScript 会报错,因为它没有满足接口的要求。
Q7: 能否使用其他命名而不是 _serviceBrand
?
技术上可以,但 _serviceBrand
是 VSCode 的约定:
- 一致性:整个代码库使用相同的命名。
- 可识别性:开发者一看就知道这是服务品牌标记。
- 下划线前缀:表示这是一个特殊的、内部使用的属性。
如果你在自己的项目中使用这个模式,可以选择其他名称,如 _brand
、_tag
、__type
等,但保持一致性很重要。
如何在自己的项目中应用这个模式
如果你想在自己的 TypeScript 项目中应用这个模式,以下是一个完整的实现指南。
步骤 1:定义基础类型
typescript
// di/types.ts
export type BrandedService = { _serviceBrand: undefined };
export interface ServiceIdentifier<T> {
(...args: any[]): void;
type: T;
}
步骤 2:创建服务装饰器工厂
typescript
// di/serviceDecorator.ts
import { ServiceIdentifier } from './types';
const serviceIds = new Map<string, ServiceIdentifier<any>>();
export function createServiceDecorator<T>(serviceId: string): ServiceIdentifier<T> {
if (serviceIds.has(serviceId)) {
return serviceIds.get(serviceId)!;
}
const id = <any>function (target: Function, key: string, index: number) {
if (arguments.length !== 3) {
throw new Error('Service decorator can only be used on constructor parameters');
}
// 存储依赖信息(简化版)
Reflect.defineMetadata('di:dependencies', id, target);
};
id.toString = () => serviceId;
serviceIds.set(serviceId, id);
return id;
}
步骤 3:定义服务接口
typescript
// services/ILogService.ts
import { createServiceDecorator } from '../di/serviceDecorator';
export const ILogService = createServiceDecorator<ILogService>('logService');
export interface ILogService {
readonly _serviceBrand: undefined;
info(message: string): void;
error(message: string): void;
debug(message: string): void;
}
步骤 4:实现服务
typescript
// services/ConsoleLogService.ts
import { ILogService } from './ILogService';
export class ConsoleLogService implements ILogService {
declare readonly _serviceBrand: undefined;
info(message: string): void {
console.log('[INFO]', message);
}
error(message: string): void {
console.error('[ERROR]', message);
}
debug(message: string): void {
console.debug('[DEBUG]', message);
}
}
步骤 5:使用依赖注入
typescript
// services/UserService.ts
import { ILogService } from './ILogService';
export class UserService {
constructor(
@ILogService private readonly logger: ILogService
) {
this.logger.info('UserService initialized');
}
async getUser(id: string): Promise<User> {
this.logger.debug(`Fetching user: ${id}`);
// ... 实现逻辑
}
}
完整示例项目结构
perl
my-project/
├── src/
│ ├── di/
│ │ ├── types.ts # 基础类型定义
│ │ ├── serviceDecorator.ts # 服务装饰器
│ │ └── container.ts # DI 容器(可选)
│ ├── services/
│ │ ├── ILogService.ts # 日志服务接口
│ │ ├── ConsoleLogService.ts # 日志服务实现
│ │ ├── IConfigService.ts # 配置服务接口
│ │ ├── ConfigService.ts # 配置服务实现
│ │ └── UserService.ts # 业务服务
│ └── main.ts # 应用入口
└── tsconfig.json
总结
readonly _serviceBrand: undefined
是 VSCode 源码中一个精巧的设计模式,它解决了 TypeScript 结构化类型系统在大型项目中的一个核心问题:如何在结构化类型系统中确保类型的名义唯一性。
核心价值:
- 类型安全:防止结构相似但语义不同的服务被错误互换
- 编译时保证:所有类型错误在编译时捕获,而非运行时
- 零运行时开销:完全是编译时特性,不影响性能
- 架构清晰:明确标识服务接口,提升代码可读性
设计理念:
这个模式体现了零成本抽象(Zero-Cost Abstraction)的设计哲学:在不影响运行时性能的前提下,通过类型系统提供强大的编译时保证。它简单而精妙------只是一个属性声明,却解决了依赖注入系统中的类型唯一性问题。
适用场景:
- 大型 TypeScript 项目,需要强类型保证
- 使用依赖注入架构的系统
- 有大量结构相似接口的代码库
- 需要明确架构约定的团队协作项目
当你下次阅读 VSCode 源码,或者设计自己的 TypeScript 架构时,希望这篇文章能帮助你更好地理解和应用这个模式。
参考资源
官方文档
- TypeScript Handbook - Type Compatibility
- TypeScript Handbook - Decorators
- TypeScript Handbook - Declaration Merging
VSCode 源码
相关文章
- Branded Types in TypeScript
- Nominal Typing Techniques in TypeScript
- Advanced TypeScript: Branded Types
- TypeScript: Phantom Types
相关技术
写在最后
感谢你读到这里!_serviceBrand
只是 VSCode 源码中众多精妙设计之一。在这个庞大的代码库中,还隐藏着无数值得学习的架构模式和工程智慧。
读完这篇文章,我很好奇你的想法:
- 你在自己的项目中遇到过类似的类型安全问题吗? 是如何解决的?
- 你觉得这个模式有哪些可以改进的地方? 或者有更好的替代方案?
- 你还想了解 VSCode 源码中的哪些设计? 依赖注入系统、插件机制、Monaco Editor 架构?
- 你会在自己的项目中应用这个模式吗? 遇到了什么挑战?
欢迎在评论区分享你的经验和看法!这是「VSCode 源码寻宝」专栏的第一篇文章,接下来我会继续挖掘更多藏在代码里的设计智慧。如果你对某个主题特别感兴趣,也欢迎在评论区告诉我。
期待与你交流!