Typescript: 使用扩展运算符时,编译器错误地报告参数/调用目标签名不匹配

创建于 2015-08-03  ·  55评论  ·  资料来源: microsoft/TypeScript

使用散布运算符时,编译器错误地报告参数/调用目标签名不匹配。 将此代码粘贴到TypeScript Playground:

function foo(x: number, y: number, z: number) { }
var args = [0, 1, 2];
foo(...args);

产生此错误:

Supplied parameters do not match any signature of call target.
Bug Fixed

最有用的评论

@ jpike88我知道您的意思很好,但您可能不会意识到这样的声明适得其反。 问题是他们的TODO看起来像里程碑2.7或2.8。 这似乎是一件显而易见的事情,但是当您有成千上万件显而易见的事情要修复时,您可能无法同时全部解决它们,而无法继续改进他们期望的其他事情。 表达投票的最佳方法是对顶部的原始帖子做出反应。 毕竟这是免费软件,他们不欠我们任何东西。 也就是说,这只是我的观点,另一个用户
像你这样的TS🕺

所有55条评论

argsany -type时也是如此

function foo(x: number, y: number, z: number) { }

function bar(...args) {
    foo(...args); // Supplied parameters do not match any signature of call target.
}

目前这是设计使然。 但是我们应该重新考虑。

@smashdevcode@tjoskar ,我正在寻找此功能的一些实际用途。 具体来说,您是否希望散布数组或元组(或两者都散布)? 以下是一些玩具示例:

//// arrays ////
var a1: number[] = [1,2];
var a2: number[] = [1,2,3];
var a3: number[] = [];
function twoNumbersOrSo(n?: number, m?: number) {
  return (n || -1) * (m || -1);
}
function howManyNumbersLessOne(n?: number, ...ns: number[]) {
    return ns.length;
}

//// tuples ////
var t1: [number, string] = [1, "foo"];
var t2: [number, string, string] = [1, "foo", "bar"];
function veryTraditional(n: number, s: string) {
    for (let i = 0; i < n; i++) {
        console.log(s);
    }
}
function moreInteresting(n: number, s: string, ...rest: string[]) {
    veryTraditional(n, s);
    console.log(rest);
}

现在,任何a1,a2,a3都可以应用于前两个函数,而任何t1,t2都可以应用于后两个函数。

注意,使用数组:

  1. 具有单一类型,因此参数都必须为同一类型。 (我想这可能是any 。)
  2. 数组的长度在编译时未知,因此参数必须是可选的。 传播论点必须是最后一个论点(就像今天一样)。

与元组:

  1. 该值有时必须具有类型注释。 否则,它将被解释为一个数组(例如(number | string)[]而不是[number, number, string] )。 在玩具代码中,这消除了简洁性的任何节省,但是在已经定义了很多接口的真实项目中,这可能是可以的。
  2. 该长度是提前知道的,因此元组可以应用于任何类型的参数列表。

@sandersn

抱歉迟了回应。 我完全想念您的答复。
我只使用一种数据类型的数组,所以元组版本对我来说不是很有趣。
但是我认为函数调用中的传播操作应该接受任何可迭代的对象。 因此,它应该与例如。 mapset ,在这种情况下,即使数据包含多种数据类型(例如,new Set()。add(1) .add('string'))。 但这也许是另一张票?

Set和传播操作的示例(在Chrome 46中有效)

function foo(...args) {
    console.log(...args);
}
var bar = new Set().add(1).add(2);

foo(...bar); // 1, 2

是的,这是一个单独的问题。 Typescript已经支持数组作为rest参数的类型,这需要使它通用。 我们只是在讨论可以在何处使用扩展参数。

您是否要将同类的数组/集合传递给具有rest参数或带有固定数量参数的函数? 我想更详细地说明您要打电话的事情。

首先,今天早上我正在与传播运算符一起玩耍,我的大多数用例在最新版本的打字稿[email protected]http://www.typescriptlang.org/Playground上都可以正常工作。 因此,这不再重要。

然而,
我不希望将所有参数都设为可选,只是为了能够使用
传播运营商,但我明白问题所在; 数组的长度在编译时未知
在编译时无法检查参数。

但是,我认为如果参数不需要是可选的并且编译器会很好
仅检查“ spread-variable”是否为正确类型,例如。 数[]

function fun1(x: number, y: number, z: number) {
    return x+y+z;
}

let arr1 = [1, 2, 3];
let arr2 = [1, 2];

fun1(...arr1);
fun1(...arr2); // OK since `arr2` is number[]
fun1(1, 2); // Should cause an error

那可能吗?

以下代码在当前版本的打字稿中有效,但在解决此问题时无效。

function fun1(...num) {
    return Math.max(...num);
}

function fun2(a1, ...num) {
    return Math.max(...num);
}

function fun3(a1, ...num) {
    return fun1(...num);
}

let arr = [1, 2, 3];

if (Math.random() < .5) {
    arr.push(1);
}

fun1(...arr);
fun2('first param', ...arr);
fun3('first param', ...arr);

也许是一个更现实的示例(现在也适用):

const doSomeWork = ({title, genre, runtime}) => { console.log(title, genre, runtime); };

function fun1(...num) {
    num.forEach(doSomeWork);
}

const data = [{
    title: 'title',
    genre: ['action', 'drama'],
    runtime: 100
}];

fun1(...data);

:+1:在Date构造函数的特定用例中,存在相同的问题:

let dateNumberArray: Array<number> = [2015,11,11];
let myNewDate = new Date(...dateNumberArray);

对于我们来说,我们使用点差允许用户在基本应用中配置argv要求,而无需构建自己的帮助程序。 例如:

{
  "yargv": [
    [
      "path",
      {
        "demand": true,
        "describe": "Source",
        "default": ".",
        "alias": "p"
      }
    ]
  ]
}
get appArgs() : { yargv: Array<Array<any>>, chdir: Array<boolean | string> } {
  return require(
    "../args.json"
  )
}

argv() : Promise<{}> {
  return new Promise((resolve) => {
    this.appArgs.yargv.forEach(v => yargs.option(...v))
    return resolve(yargs.usage("$0 [args]").help("h").
      alias("h", "help").argv)
  })
}

一个使用https://github.com/ReactiveX/rxjsCombineLatest函数的真实示例,在projectFn并保留rest参数来保留this值时使用传播参数。

getCombination() {
    return this.firstObservable
      .combineLatest(this.secondObservable, (...args) => this.projectFn(...args)));
  }

目前我要做

getCombination() {
    return this.firstObservable
      .combineLatest(this.secondObservable, (...args) => this.projectFn.apply(this, args)));
  }

FWIW,另一个现实世界的示例正在尝试使用来自lodash的合并来深度合并对象数组:

import { merge } from 'lodash';

...
return merge(...arrayOfObjects);

这是另一个真实的用例; 我们正在尝试重构看起来像这样的代码:

return Observable
      .forkJoin(a, b)
      .map(([current, past]) => this.mergeData(current, past));

变成略显优雅:

return Observable
      .forkJoin(a, b)
      .map(data => this.mergeData(...data));

但是使用散布运算符会触发签名不匹配错误。

另一个例子

class Parent {
    constructor(a, b, c){

    }
}

class Child extends Parent {
    constructor(d, ...args) {
        super(...args);
    }
}

TS2346:提供的参数与呼叫目标的任何签名都不匹配。

重载方法的相同问题:

listen(port: number, hostname?: string, backlog?: number, listeningListener?: Function): Server;
listen(port: number, hostname?: string, listeningListener?: Function): Server;
listen(port: number, backlog?: number, listeningListener?: Function): Server;
listen(port: number, listeningListener?: Function): Server;
listen(path: string, backlog?: number, listeningListener?: Function): Server;
listen(path: string, listeningListener?: Function): Server;
listen(options: ListenOptions, listeningListener?: Function): Server;
listen(handle: any, backlog?: number, listeningListener?: Function): Server;
listen(handle: any, listeningListener?: Function): Server;

虽然称为

myListen(...args) {
    listen(...args);
}

然后:_ [ts]提供的参数与调用目标的任何签名都不匹配。_

因此,目前尚无法执行以下操作:

export type DateProp = Date | (string|number)[];

const setDate = (value: DateProp): Date => (
    isDate(value) ? value : new Date(...value)
);

由于产生错误:

提供的参数与呼叫目标的任何签名都不匹配。

即使值包含正确的日期构造函数参数值@mhegazy ? 还是我没有得到什么?

即使值包含正确的日期构造函数参数值@mhegazy ? 还是我没有得到什么?

没错,这就是此问题正在跟踪的内容。

我觉得有义务问我是否可以提供帮助/做出贡献。 如果现在,您能否详细说明为什么在这种情况下会导致错误? 可以通过config跳过吗?

当我将.js文件重命名为.ts时,实际上遇到了这个问题。 无论如何,“不是所有有效的JS都是有效的TS”的一个例子。

我目前在实现样式化组件的媒体模板example时遇到这个问题。

import {css} from 'styled-components';

export const Breakpoints = {
    tablet: 580,
    desktop: 800,
};

export type BreakpointLabels = keyof typeof Breakpoints;

export const media = Object.keys(Breakpoints).reduce((mediaQueries, label: BreakpointLabels) => (
    {
        ...mediaQueries,
        [label]: (...args: any[]) =>
            css`
                <strong i="7">@media</strong> (max-width: ${Breakpoints[label]}px) {
                    ${css(...args)}
                      ^^^^^^^^^^^^ Supplied parameters do not match any signature of call target.
                }
            `
    }
), {});

我的解决方法是使用css.call ,它至少与any[] -typed args一起使用:

import {css} from 'styled-components';

export const Breakpoints = {
    tablet: 580,
    desktop: 800,
};

export type BreakpointLabels = keyof typeof Breakpoints;

export const media = Object.keys(Breakpoints).reduce((mediaQueries, label: BreakpointLabels) => (
    {
        ...mediaQueries,
        [label]: (...args: any[]) =>
            css`
                <strong i="13">@media</strong> (max-width: ${Breakpoints[label]}px) {
                    ${css.call(this, ...args)}
                }
            `
    }
), {});

同样的问题,我的示例是基本合并功能:

merge(target: T, ...sources: T[])

有什么解决方法,至少每个文件如何忽略此错误?

实际上,我想看看为什么重新考虑此ES6可能性而被设计或任何计划打破的原因。 @mhegazy,请发表评论。

我们最近进行了一项更改,以允许在目标都是可选的情况下扩展到调用表达式中(请参阅https://github.com/Microsoft/TypeScript/pull/15849)。 通过将扩展输出视为无限的可选参数集来进行更改。

进行此更改后,以下是示例中的当前状态:

declare var args: number[];

function foo(x?: number, y?: number, z?: number) { }
foo(...args);     // OK
foo(2, ...args);  // OK

function bar(...args: number[]) { }
bar(...args);     // OK
bar(2, ...args);  // OK

function baz(x: number, y: number, z: number) { }
baz(...args);     // still not allowed

在最后一个示例中,编译器无法验证args满足所需的baz ,因为args长度无法静态验证。

该区域剩下的是允许具有已知大小的元组满足具有相同长度的必需参数的函数。但这并不像听起来那样简单。 例如:

function baz(x: number, y: number, z: number) { }
var tuple: [number, number, number] = [1, 2, 3];
baz(...tuple);     // should be allowed

我有类似@devrelm的问题

我仍然对此有疑问,怎么了?

class A {
    constructor(message: string, test?: any) {
        console.log('A constructor called');
    }
}

class B extends A {
    constructor(...spread) {
        super('a', ...spread);
    }
}

它在打字稿操场上出现错误

@owlcode在TS 2.4中,但是游乐场在TS 2.3上,直到2.4版本发布为止。

这确实在TS 2.4中已解决,

export function log(...args: any[]) {
    console.log(...join(args.map(formatDevTools),' '));
}

但是, TS 2.4中似乎存在一个新错误:

TS2461:类型为“可迭代”'不是数组类型。

当您尝试传播可迭代对象( [...obj] )时发生。

但是2.4.1-insiders.20170615中有另一个错误(我认为),我还无法弄清楚。

我也不知道该如何解决。 将数组强制转换为any[]将无济于事。


没关系,我们可以修复Console的定义,

interface _Console {
    assert(test?: boolean, message?: string, ...optionalParams: any[]): void;
    clear(): void;
    count(countTitle?: string): void;
    debug(...optionalParams: any[]): void;
    dir(value?: any, ...optionalParams: any[]): void;
    dirxml(value: any): void;
    error(...optionalParams: any[]): void;
    exception(message?: string, ...optionalParams: any[]): void;
    group(groupTitle?: string): void;
    groupCollapsed(groupTitle?: string): void;
    groupEnd(): void;
    info(...optionalParams: any[]): void;
    log(...optionalParams: any[]): void;
    msIsIndependentlyComposed(element: Element): boolean;
    profile(reportName?: string): void;
    profileEnd(): void;
    select(element: Element): void;
    table(...data: any[]): void;
    time(timerName?: string): void;
    timeEnd(timerName?: string): void;
    trace(...optionalParams: any[]): void;
    warn(...optionalParams: any[]): void;
}

然后转换console对象:

export function log(...args: any[]) {
    (console as _Console).log(...join(args.map(formatDevTools),' '));
}

现在必须要做。

我在#18004为此打开了一个PR。

现在,我必须在tsconfig中启用"noStrictGenericChecks"

现在,我必须在tsconfig中启用“ noStrictGenericChecks”。

不确定我是否知道此错误与此错误的关系...

不知道我是否看到了与此错误的关系。

@mhegazy此设置禁用所有函数签名,因此我可以使用散布运算符传递参数而无需匹配参数数量(至少不是静态地)

那不是--noStrictGenericChecks所做的。 请参阅https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#stricter -checking-for-generic-functions

我只是碰到这个。

虽然我可以理解这很困难,但至少应该可以识别“过多”的论点。

从现实世界的角度来看, @ sandersn我们正在重构API以接受单个参数或数组,而不是使用rest参数。 因此,我们传递诸如foo(value, ...otherValues)情况不会被识别为错误。 具体来说,这似乎很危险

declare const foo: (value: string) => string;

foo('bar', 'qat'); // Expected 1 arguments, but got 2.

foo('bar', ...['qat']); // No error

@kitsonk您可以

即使检查长度,我仍然会收到此错误:

function myFunc(...args: any[]) {
  if(args.length > 0) {
    otherFunc(...args)
  }
}

编辑:

为了消除混乱,我从这里重定向到,它指出:

预期有2个参数,但至少为0

该问题被视为该问题的重复。

就我而言,我可以通过添加function otherFunc()作为初始化程序之一来使任何内容都不传递给方法,从而解决错误(更像是黑客而不是修复程序):

function otherFunc()
function otherFunc(arg1: string)
function otherFunc(arg1: string, arg2: number)
function otherFunc(...args: any[]) {
  // Do stuff
}

该解决方法在2.7上已被打破

这仍然是一个问题吗? 散布运算符是在JS中完全兼容的工作方式,因此TS应该适当地考虑它。

@ jpike88我知道您的意思很好,但您可能不会意识到这样的声明适得其反。 问题是他们的TODO看起来像里程碑2.7或2.8。 这似乎是一件显而易见的事情,但是当您有成千上万件显而易见的事情要修复时,您可能无法同时全部解决它们,而无法继续改进他们期望的其他事情。 表达投票的最佳方法是对顶部的原始帖子做出反应。 毕竟这是免费软件,他们不欠我们任何东西。 也就是说,这只是我的观点,另一个用户
像你这样的TS🕺

另一个不错的测试用例(实际上是TS 2.7的回归)是:

class NonEmptyArray<T> extends Array<T> {
        0: T;
}

function c(firstArg: string, ... plainValues: string[]): string;
function c(): undefined;
function c(...values: string[]): string | undefined {
        if (!values.length) {
                return undefined;
        }
        return "";
}


function d(): NonEmptyArray<string> {
        return [""];
}

function y(): string | undefined {
        return c(...d());
}

它产生

test.ts(20,9):错误TS2557:预期至少有0个参数,但有0个或更多。

我遇到的另一个有关传播算子的问题:

const get = (id?: string = '1231254', bar?: Bar, baz?: Baz) => {...}
const foo: [undefined, Bar, Baz] = [undefined, bar, baz]

get(...foo) // [ts] Argument of type 'Bar | Baz' is not assignable to parameter  
of type 'string'.
Type 'Baz' is not assignable to type 'string'

当然,在ES6中,我可以毫无问题地散布未定义的数组作为参数并正确传递undefined但是似乎TS散布它们时忽略了undefined值。

另一个真实的示例,其中在函数调用中正确处理传播运算符将有很大帮助:
我有NSwagStudio生成的函数-C#WebApi的API生成器,具有相同的参数(GET参数在结构中定义)

生成的函数如下所示:

export interface ITableApiClient {
    getTableInfo(objnam: string | null, objsch: string | null | undefined, dbnam: string | null, conid: number | undefined): Promise<FileResponse | null>;
    otherFunction(objnam: string | null, objsch: string | null | undefined, dbnam: string | null, conid: number | undefined): Promise<string | null>;
}

我想用语法来称呼它

   get tableIdentification() {
       return [this.name, this.schema, this.databaseName, this.serverConnectionId];
   }
...
   return apiClient.getTableInfo(...this.tableIdentification);

   // this doesn't help, still compile-time error
   // return apiClient.getTableInfo(...(this.tableIdentification as any));

因为还有更多具有完全相同参数的函数(因为它们是由C#后端中定义的相同参数类生成的)。 现在我必须将属性表的主体复制n次到每次使用中

@smashdevcode对我来说解决方案是添加@ ts-ignore

function foo(x: number, y: number, z: number) { }
var args = [0, 1, 2];
// @ts-ignore
foo(...args);

此处的示例: https :

@ darekf77 ,或者如果您说args是一个元组:

function foo(x: number, y: number, z: number) { }
const args: [number, number, number] = [0, 1, 2];
foo(...args);

ts游乐场

@ darekf77 ,或者如果您说args是一个元组:

function foo(x: number, y: number, z: number) { }
const args: [number, number, number] = [0, 1, 2];
foo(...args);

当我在命令行中编译打字稿时,这对我有用,但是当我在打字时Visual Studio Code验证打字稿时,这对我来说无效。 我不确定这里有什么断开连接。

自jayphelps提到将在即将发布的版本中解决此问题以来,已经过去了一年多,但是仍然会发生此问题。 有什么更新吗? 我知道我可以使用@ ts-ignore,但是这会删除该函数的所有打字稿消息,因此并不是真正合适的解决方案。

如tjoskar所述,@ TidyIQ将您的扩展数组转换为元组。

该数组不是固定长度的,因此将无法正常工作。

就算编译器不了解,类型转换也将起作用。 我同意这不是最佳解决方案。

(这个问题可能应该移到堆栈溢出(或类似的地方))

@TidyIQ ,如果数组没有固定长度,则编译器无法确定该数组是否合适。

function foo(x: number, y: number, z: number) { }
const args = arrayOfSomeLength;
foo(...args); // Error: The compiler do not know if there is 3 or 2 or 1 or 50 elements in the array. 

因此,如果数组具有动态长度,则参数也应为:

function foo(...args: number[]) { }
const args = arrayOfSomeLength;
foo(...args);

操场

不幸的是,由于我的一些参数是固定长度的,因此也不起作用。

也许如果我只是发布代码,您可能会看到问题。

interface IF_Object {
  [key: string]: object;
}
//  VALUE SHOULD BE REQUIRED BUT MADE OPTIONAL TO BYPASS TYPESCRIPT ERROR
interface IF_SetKV {
  (key: string, value?: any): Function;
}
//  VALUE SHOULD BE REQUIRED BUT MADE OPTIONAL TO BYPASS TYPESCRIPT ERROR
interface IF_CreateState {
  (key: string, value?: any, ...more: string[]): Function;
}

const setKV: IF_SetKV = (key, value) => (object: IF_Object = {}) => ({
  ...object,
  [key]: value
});

const createState: IF_CreateState = (key, value, ...more) => (
  object: IF_Object = {}
) =>
  more.length === 0
    ? setKV(key, value)(object)
    : setKV(key, createState(value, ...more)(object[key]))(object);

// TYPESCRIPT ERROR OCCURS HERE. CAN ONLY BE REMOVED WITH TS<strong i="7">@IGNORE</strong>
const newState = createState(...stateList, action.payload.value)(reduced);

@TidyIQ ,我不确定是否遵循。 如果stateList是一个空数组会怎样? 在这种情况下, action.payload.value将作为key传递?

不应该这样工作:

const createState = (...args: string[]) => (object: IF_Object = {}) => {
  const [key, value, ...rest] = args;
  if (rest.length === 0) {
    setKV(key, value)(object)
  } else {
    setKV(key, createState(value, ...rest)(object[key]))(object);
  }
}

将要传递的值转换为要传递参数的函数的ParameterType

const add = (a: number, b: number) => a + b

const values = {
  a: 1,
  b: 2,
}

type AddParams = Parameters<typeof add>

add(...(values) as AddParams)

@ mateja176这似乎也可行,并且可能更适合某些用例。

add(...(values as [number,number]))

要么

add(...(values as [any,any]))

我为此使用辅助类型来处理任意数组,而不必显式键入它们:

type NonEmptyArray<T extends any[]> = T extends (infer U)[]
  ? [U, ...U[]]
  : never

可以这样使用:

add(...values as NonEmptyArray<typeof values>)


初学者的详细说明

以下对NonEmptyArray类型的剖析详细说明了其工作原理:

#| 零件| 说明
-|-|-
(1)| type NonEmptyArray | 助手类型的名称
(2)| <T | 辅助类型采用通用类型参数T
(3)| extends any[]> | 要被类型检查器接受,该类型参数T必须为某种数组类型。
(4)| T extends (infer U)[] ? | 我们的助手类型是条件类型,它检查T实际上是数组类型。
我们在(3)中已将T为数组类型,因此此条件始终会通过,但是使用条件可以让TypeScript infer成为T数组的类型。由制成,我们称之为U
(5)| [U, ...U[]] | 现在我们可以组装结果类型:一个数组,其中第一个条目的类型U ,其余(0个或更多)条目的类型也为U
由于这种特定的元组符号,TypeScript知道至少有一项。
(6)| : never | 这只是语法上完成条件所需要的。 请记住:条件语句只是提取U类型的一个技巧,它始终会通过。 因此,我们可以通过产生never来安全地忽略“ else”分支。


现在,如果我们这样做...

ts const values = [1,2,3] add(...values as NonEmptyArray<typeof values>)

...将发生以下情况:

  • typeof values是TypeScript为values数组推断的类型,该数组是一个数字数组: number[]
  • 这意味着,我们将number[]作为T传递。 (2/3)
  • T确实是数组类型,因此,我们可以从中推断出U ,即number 。 (4)
  • 现在我们知道Unumber ,我们得出类型[number, ...number[]] 。 (5)

@tjoskar将您的代码更改为

function foo(x: number, y: number, z: number, f: number) { }
const args: [number, number, number] = [0, 1, 2];
foo(...args, 3);

游乐场
错误又回来了。

令人惊讶的是,如果将最后一行更改为foo(3, ...args); -不会有错误。

我觉得这还是行不通的。 这是我的例子

onSetValue={(...args: Parameters<typeof props.onSetValue>) => {
    setLanguage(null);
    props.onSetValue(...args); // Expected 2 arguments, but got 0 or more.
  }}

props.onSetValue的类型是什么并不重要,因为我只是接受参数类型并将其传递给我从中获得类型的函数,它仍然会给出Expected 2 arguments, but got 0 or more.错误。

游乐场链接

这是@ Haaxor1689示例的简化形式:
游乐场链接

我仍然无法正常工作

这是我的临时解决方法

class Board {
  private events: Events

  public on(...args: Parameters<this['events']['on']>) {
    this.events.on.call(this.events, ...args)
  }
}
此页面是否有帮助?
0 / 5 - 0 等级

相关问题

fwanicka picture fwanicka  ·  3评论

jbondc picture jbondc  ·  3评论

CyrusNajmabadi picture CyrusNajmabadi  ·  3评论

blendsdk picture blendsdk  ·  3评论

dlaberge picture dlaberge  ·  3评论