Typescript 函数/对象参数

7
为什么 TypeScript ES6 没有检测到对象不是函数?
find: (collection: string, query: object, sortQuery = {}, cb?: Function)  => {
    socketManager.call('find', collection, query, sortQuery, cb);
}

根据这个函数,您会认为这将失败:

this._services._socket.methods.find('vendors', {type: 'repair'}, (errVen, resVen) => {}

因为没有sortQuery对象,而是一个回调函数。这不会给我任何类型的错误,并且意味着TypeScript将回调作为对象类型允许。

我如何确保这会导致错误?


它是否实际推断sortQuery为对象类型?还是任何类型? - Jonas Wilms
这里发生了同样的情况,可能是因为在JavaScript中函数是对象:https://www.typescriptlang.org/play/#src=let%20fn%20%3D%20(x%20%3D%20%7B%7D)%20%3D%3E%20%7B%20%7D%20%0D%0A%0D%0Afn(()%20%3D%3E%20%7B%20%7D)%3B - Frank Modica
使用以下代码可以得到相同的结果: find: (collection: string, query: object, sortQuery: object, cb?: Function) => { socketManager.call('find', collection, query, sortQuery, cb); } - user1779362
有没有办法确保“Function”类型而不是对象? - user1779362
2
你知道sortQuery的参数和返回类型吗?你可以更具体地定义它。 - Frank Modica
@user1779362,您使用类型来定义您想要的内容,而不是您不想要的内容。如果调用者传递了一个具有sortQuery所需属性的函数,那么这不是有效的吗?无论它是一个函数,您都不会将其用作函数,重要的是它具有您将要使用的属性。 - user310988
2个回答

5

对象和函数本质上是相同的东西,但类型帮助我们区分它们的功能。

考虑以下示例:

const foo1: { (): string } = () => "";

变量foo的类型是object,但这个对象是可调用的...它是一个函数,确实可以被调用,但你不能在其上设置一个名为bar的属性。
foo1(); // This works
foo1.bar = 5; // This, not so much.

此外还需考虑以下事项:
const foo2: { bar?: number; } = {};

变量foo有一个名为bar的属性。该属性可以设置,但对象不能被调用,因为它没有被定义为可调用类型。
foo2.bar = 5; // This works
foo2(); // This, not so much.

所以,让我们来看一下您原始的打字内容:
sortQuery = {}

sortQuery是一个对象,但这就是我们所知道的全部。它没有任何属性,也不能调用,只是一个对象。

我们已经知道函数一个对象,因此你可以将一个函数分配给它。但你不能调用它,因为它没有定义为可调用的。

const sortQuery: {} = () => ""; // This works.
sortQuery(); // This, not so much.
sortQuery.bar = 5; // Nor this.

如果您完全控制源代码,那么解决此问题的一种方法是将多个参数转换为带有命名属性的单个参数:
const foo = (params: { collection: string, query: object, sortQuery: {}, cb?: Function }) => { };

foo({ collection: "", query: {}, sortQuery: {} }); // Fine
foo({ collection: "", query: {}, sortQuery: {}, cb: () => { } }); // Fine
foo({ collection: "", query: {}, cb: () => { } }); // Not Fine

这样做通过要求使用名称而不是依赖于函数调用中的位置来消除任何歧义。

1
这也是使用对象的好解决方案。不幸的是,这需要花费相当多的时间来重新处理所有的调用/创建脚本来修复所有这些问题。不过,这是一个好答案。 - user1779362
2
最好使用解构参数 fnName( { p1, p2, p3 } : { p1: string, p2: number, p3: boolean }) - TheGeekZn

5

使用TypeScript的条件类型 (TS v2.8),我们可以使用Exclude<T, Function>来从object类型中排除Functions

let v = {
  find: <T extends object>(collection: string, query: object, sortQuery: Exclude<T, Function>, cb?: (a: string, b: string) => void) => {
  }
}

// Invalid
v.find('vendors', { type: 'repair' }, (a, b) => { })
v.find('vendors', { type: 'repair' }, 'I am a string', (a, b) => { })

// Valid
v.find('vendors', { type: 'repair' }, { dir: -1 })
v.find('vendors', { type: 'repair' }, { dir: -1 }, (a, b) => { })

一个默认的参数值可以这样设置:

sortQuery: Exclude<T, Function> = <any>{}

如下图所示,前两次调用find方法会抛出错误,但后两次调用不会抛出错误:

TypeScript Exclude

然后显示的错误如下所示:
  • [ts] 类型“(a, b) => void”的参数不能赋给类型“never”的参数。 [2345]
  • [ts] 类型““I am a string””的参数不能赋给类型“object”的参数。 [2345]

这是一个很好的解决方案。所以我没有提到的一件事是,我有一个脚本正在运行所有我的服务器函数,并解析文件以获取函数名称和参数,以在客户端生成函数。如果我更改我的脚本以在所有函数上执行此操作,是否可以? - user1779362
find: <T extends object>(collection: string, query: object, sortQuery: Exclude<T, Function> = <any>{}, cb?: Function) => { socketManager.call('find', collection, query, sortQuery, cb); }, findWithOptions: (collection: string, query: object, options: PaginationOptions, cb?: Function) => { socketManager.call('findWithOptions', collection, query, options, cb); }, insertDocument: (collection: string, document: object, cb?: Function) => { socketManager.call('insertDocument', collection, document, cb); }, - user1779362

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接