如何在TypeScript中用Proxy实现递归方法链式调用?
为Proxy实现的链式调用添加TypeScript类型定义
问题背景
有一个基于Proxy实现的原生JavaScript链式调用工具,支持通过动态属性拼接链路,每次调用都会返回新对象保持不可变性,JS原版代码如下:
const ChainHandler = { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } return target.link(prop); } }; class Chain { constructor(links = []) { this.links = links; return new Proxy(this, ChainHandler); } link(next) { return new Chain([...this.links, next]); } log() { console.log(this.links.join("-")); } } const chain = new Chain(); chain.you.can.use.dynamic.properties.log(); // 输出:you-can-use-dynamic-properties
尝试迁移到TypeScript时遇到类型报错,TS代码及错误如下:
const ChainHandler: ProxyHandler<Chain> = { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } return target.link(prop as string); } }; class Chain { private links: string[]; constructor(links: string[] = []) { this.links = links; return new Proxy(this, ChainHandler); } link(next: string) { return new Chain([...this.links, next]); } log() { console.log(this.links.join("-")); } } const chain = new Chain(); chain.you.can.use.dynamic.properties.log(); // 错误:Property 'you' does not exist on type 'Chain'
提问者尝试过两种方式但未解决:
- 类型断言仅支持第一层动态属性:
const chain = new Chain() as Chain & { [key: string]: Chain }; chain.you.can.use.dynamic.properties.log(); // 错误:Property 'can' does not exist on type 'Chain'
- 递归类型也未生效:
type ProxiedClass<C> = C & { [key: string]: ProxiedClass<C> }; const chain = new Chain() as ProxiedClass<Chain>; chain.you.can.use.dynamic.properties.log(); // 错误:Property 'can' does not exist on type 'Chain'
解决方案
要让TypeScript识别无限层级的动态属性链式调用,关键是定义递归的类型签名,告诉TS任意字符串属性都会返回一个可继续链式调用的实例。
方案1:定义递归接口
// 定义递归接口,继承Chain的所有方法,同时支持任意字符串属性返回自身 interface ChainProxy extends Chain { [key: string]: ChainProxy; } const ChainHandler: ProxyHandler<Chain> = { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } // 把新创建的Chain实例断言为ChainProxy return target.link(prop as string) as ChainProxy; } }; class Chain { private links: string[]; constructor(links: string[] = []) { this.links = links; // 将Proxy实例断言为ChainProxy,让TS识别正确类型 return new Proxy(this, ChainHandler) as unknown as ChainProxy; } link(next: string) { return new Chain([...this.links, next]); } log() { console.log(this.links.join("-")); } } // 此时chain自动拥有ChainProxy类型,支持无限层级链式调用 const chain = new Chain(); chain.you.can.use.dynamic.properties.log(); // 无类型错误
方案2:声明合并(更简洁)
直接通过声明合并给Chain类添加递归索引签名:
// 声明合并,为Chain添加递归索引签名 declare class Chain { [key: string]: Chain; } const ChainHandler: ProxyHandler<Chain> = { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } return target.link(prop as string); } }; class Chain { private links: string[]; constructor(links: string[] = []) { this.links = links; return new Proxy(this, ChainHandler); } link(next: string) { return new Chain([...this.links, next]); } log() { console.log(this.links.join("-")); } } const chain = new Chain(); chain.you.can.use.dynamic.properties.log(); // 无类型错误
关键说明
- 递归类型的核心是:任意字符串属性返回的对象和原实例类型一致,这样TypeScript就能推断出无限层级的链式调用是合法的。
- 方案1通过单独的接口明确分离“基础类”和“代理类型”,结构更清晰;方案2利用声明合并直接扩展类的类型,代码更简洁。
内容的提问来源于stack exchange,提问作者ZDV
相关产品推荐
相关产品推荐

