typescript 类型为“string”的参数|number'不能分配给类型为' number '的参数,TS无法区分

46scxncf  于 2023-03-24  发布在  TypeScript
关注(0)|答案(1)|浏览(373)

在这里,我想创建一个可以定义其依赖关系的函数,这些依赖关系在示例化时提供给函数。我无法让TS在这里给予正确的类型检查。我得到的错误是:

(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'

如何让TS按顺序理解args的类型,而不是将它们作为一个union集中在一起?
Playground

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>;
}

// EXAMPLE

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);
}
zz2j4svz

zz2j4svz1#

你的问题是,当TypeScript遇到像[dep1, dep2]这样的数组文字时,默认行为是将其类型推断为像Array<typeof dep1 | typeof dep2>这样的普通数组类型,而不知道长度或顺序。这通常是人们想要的,因为数组是可变的,你可以改变它们的内容。但现在这并不可取;你更希望编译器推断一个元组类型,比如[typeof dep1, typeof dep2]
有不同的方法来实现这一点;您可以使用constAssert,如[dep1, dep2] as const,但这需要injectable()的调用者做一些事情。
另一种方法是使dependencies参数具有像readonly [...Deps]这样的可变元组类型,而不仅仅是Deps。这对 type 没有太大影响,但它提供了一个有助于推断的元组 context。正如microsoft/TypeScript#39094中提到的,pull请求引入了可变元组类型:
当数组文字的上下文类型是元组类型时,为数组文字推断元组类型。类型[...T],其中T是类似数组的类型参数,可以方便地用于指示对元组类型推断的偏好。
所以我们可以这样做:

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

这里我用readonly [...Deps]替换了Deps(注意,readonly元组类型实际上比非readonly元组类型更容易接受;readonly X[]可分配给X[],但反之亦然)。
现在你得到了你想要的行为:

const injectableObject = injectable(
  (d1, d2) => Promise.resolve(someService(d1, d2)), // okay
  //^ (parameter) d1: number
  //   ^  (parameter) d2: string
  [dep1, dep2]
);

Playground代码链接

相关问题