You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 08:17:02