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

如何让配置对象始终作为构造函数及静态方法的最后参数(TypeScript)

问题场景

我搭建了如下TypeScript代码结构,可通过Tile对象或静态方法传入坐标创建Grid实例:

type Point = { x: number, y: number }

type TileConfig = {
  thickness: number;
  color: string;
}

type GridConfig = {
  origin: Point;
  gutter: number;
}

type TileConstructor<T extends Tile> = new (coord: Point, config?: TileConfig) => T

class Tile {
  config: TileConfig
  constructor(coord: Point, {thickness = 1, color = 'BLUE'}: TileConfig = {thickness: 1, color: 'BLUE'}) {
    this.config = { thickness, color };
  }
}

class TileWithTerrain extends Tile {
  terrain!: string
}

class Grid<T extends Tile>{
  tiles = new Map<string, T>();
  setTile(tile: T) {
    this.tiles.set(tile.toString(), tile);
  };
  config: GridConfig
  
  static fromPoints<T extends Tile>(
    coords: Iterable<Point>,
    tileConstructor?: TileConstructor<T>,
    { origin = {x:0, y:0}, gutter = 0 }: GridConfig | Partial<GridConfig> | undefined = {origin: {x:0, y:0}, gutter: 0 }
  ) {}
  
  constructor();
  constructor(tiles: Iterable<Tile>, config?: GridConfig);
  constructor(tiles?: Iterable<T>, { origin = {x:0, y:0}, gutter = 0 }: GridConfig = {origin: {x:0, y:0}, gutter: 0 }) {
    this.config = {origin, gutter} ;
  };
}

const grid = Grid.fromPoints([{x:0, y:0}], { origin: { x: 0, y: 10 }})  // ERROR: 希望用户能直接用标准Tile和配置创建实例
const grid2 = Grid.fromPoints<TileWithTerrain>([{x:0, y:0}], TileWithTerrain, { origin: { x: 0, y: 10 }})  // 正常工作

目前调用Grid.fromPoints([{x:0, y:0}], { origin: { x: 0, y: 10 }})会触发类型错误,原因是可选参数tileConstructor与GridConfig类型冲突。我希望无需让用户传入undefined或Tile构造函数,就能让配置对象始终作为最后参数传递,寻求最优实现方案。

我不想让用户必须传入undefined或Tile才能使用坐标创建实例:

const grid = Grid.fromCoordinates([{ x: 0, y: 0 }], Tile, { offset: -1 })
解决方案

利用TypeScript的函数重载定义清晰的调用签名,让编译器自动区分参数场景,实现无需额外传参的友好调用方式:

class Grid<T extends Tile>{
  tiles = new Map<string, T>();
  setTile(tile: T) {
    this.tiles.set(tile.toString(), tile);
  };
  config: GridConfig
  
  // 重载1:仅传坐标+配置(默认使用Tile构造函数)
  static fromPoints(coords: Iterable<Point>, config?: GridConfig | Partial<GridConfig>): Grid<Tile>;
  // 重载2:传坐标+自定义Tile构造函数+配置
  static fromPoints<T extends Tile>(coords: Iterable<Point>, tileConstructor: TileConstructor<T>, config?: GridConfig | Partial<GridConfig>): Grid<T>;
  // 统一实现逻辑
  static fromPoints<T extends Tile>(
    coords: Iterable<Point>,
    arg2?: TileConstructor<T> | GridConfig | Partial<GridConfig>,
    arg3?: GridConfig | Partial<GridConfig>
  ): Grid<T | Tile> {
    // 初始化默认值
    let tileConstructor: TileConstructor<T | Tile> = Tile as TileConstructor<T | Tile>;
    let finalConfig: GridConfig = { origin: {x:0, y:0}, gutter: 0 };

    // 区分参数场景
    if (typeof arg2 === 'function') {
      tileConstructor = arg2;
      finalConfig = { ...finalConfig, ...arg3 };
    } else if (arg2) {
      finalConfig = { ...finalConfig, ...arg2 };
    }

    // 创建Grid并生成Tile
    const grid = new Grid<T | Tile>(undefined, finalConfig);
    for (const coord of coords) {
      grid.setTile(new tileConstructor(coord));
    }
    return grid;
  }
  
  constructor();
  constructor(tiles: Iterable<Tile>, config?: GridConfig);
  constructor(tiles?: Iterable<T>, { origin = {x:0, y:0}, gutter = 0 }: GridConfig = {origin: {x:0, y:0}, gutter: 0 }) {
    this.config = {origin, gutter} ;
  };
}

// 两种调用方式均正常工作
const grid = Grid.fromPoints([{x:0, y:0}], { origin: { x: 0, y: 10 }})  // OK
const grid2 = Grid.fromPoints<TileWithTerrain>([{x:0, y:0}], TileWithTerrain, { origin: { x: 0, y: 10 }})  // OK

方案说明

  • 通过函数重载明确两种调用模式,消除参数歧义;
  • 实现逻辑中通过判断参数类型自动分支处理,默认使用Tile构造函数;
  • 配置对象采用合并方式,保留默认值的同时覆盖用户传入的配置项;
  • 用户无需额外传入undefined或Tile,即可直接用坐标+配置创建Grid实例。

内容的提问来源于stack exchange,提问作者ChrisOram

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 18:47:14