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

TypeScript类型校验问题:如何让参数类型按顺序区分而非合并为联合类型?

问题描述

我想要创建一个可定义依赖项的函数,这些依赖会在实例化时传入函数。但目前TypeScript无法正确做类型校验,抛出以下错误:

(parameter) d1: string | number
Argument of type 'string | number' is not assignable to parameter of type 'number'.
Type 'string' is not assignable to type 'number'

怎么让TypeScript按顺序识别参数类型,而不是把它们合并成联合类型?

原代码示例

export type Injectable<T = any> = {
  dependencies: Injectable[];
  get: () => Promise<T | null>;
};

export type AsyncFactoryFn<T, Deps extends any[] = any[]> = (...args: {[K in keyof Deps]: Deps[K] extends Injectable<infer U> ? U : never}) => Promise<T>;

export function injectable<T, Deps extends Injectable<any>[]>(
  asyncFactoryFn: AsyncFactoryFn<T, Deps>,
  dependencies: Deps = [] as unknown as Deps,
): Injectable<T> {
  return {} as Injectable<T>;
}

// 示例代码
const dep1 = {
  dependencies: [],
  get: () => Promise.resolve(1),
}

const dep2 = {
  dependencies: [],
  get: () => Promise.resolve('a'),
}

const injectableObject = injectable((d1, d2) => Promise.resolve(someService(d1, d2)), [dep1, dep2]);

function someService(d1: number, d2: string) {
  console.log(d1, d2);
}

解决方案

问题核心在于Deps extends Injectable<any>[]这个约束:TypeScript默认会把数组字面量推断为数组类型(比如Injectable<number>[]),而不是保留顺序的元组类型,导致参数类型被合并成联合类型。我们需要修改类型定义,引导TypeScript推断为元组,同时正确提取每个依赖的返回类型。

修改后的代码

export type Injectable<T = any> = {
  dependencies: Injectable[];
  get: () => Promise<T | null>;
};

// 用readonly元组约束Deps,确保保留每个元素的类型顺序
export type AsyncFactoryFn<T, Deps extends readonly Injectable<any>[]> = 
  (...args: { [K in keyof Deps]: Deps[K] extends Injectable<infer U> ? U : never }) => Promise<T>;

// 使用readonly元组约束,并添加泛型默认值优化推断
export function injectable<T, Deps extends readonly Injectable<any>[] = []>(
  asyncFactoryFn: AsyncFactoryFn<T, Deps>,
  dependencies: Deps,
): Injectable<T> {
  return {} as Injectable<T>;
}

// 示例代码优化
// 方式一:用as const让TypeScript保留具体类型
const dep1 = {
  dependencies: [],
  get: () => Promise.resolve(1),
} as const;

const dep2 = {
  dependencies: [],
  get: () => Promise.resolve('a'),
} as const;

// 方式二:直接指定Injectable的泛型类型(更直观)
// const dep1: Injectable<number> = {
//   dependencies: [],
//   get: () => Promise.resolve(1),
// };
// const dep2: Injectable<string> = {
//   dependencies: [],
//   get: () => Promise.resolve('a'),
// };

const injectableObject = injectable((d1, d2) => Promise.resolve(someService(d1, d2)), [dep1, dep2]);

function someService(d1: number, d2: string) {
  console.log(d1, d2);
}

关键修改点

  1. 元组约束替换数组约束:把Deps extends Injectable<any>[]改成Deps extends readonly Injectable<any>[],并设置默认值= [],强迫TypeScript将依赖列表推断为元组,保留每个元素的类型和顺序。
  2. 锁定依赖类型:要么给依赖对象加as const断言,要么直接指定Injectable的泛型类型,避免TypeScript把类型拓宽为联合类型。
  3. 移除默认参数:原代码中dependencies: Deps = [] as unknown as Deps的默认值会干扰元组推断,直接移除即可。

修改后,d1会被正确推断为number,d2推断为string,完全匹配someService的参数类型,类型校验顺利通过。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 12:15:38