【问题标题】:Typescript Function/Object parameters打字稿函数/对象参数
【发布时间】:2019-04-29 11:00:24
【问题描述】:

为什么 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 对象而是一个回调函数。这没有给我任何类型的错误,这意味着打字稿允许回调作为对象类型。

如何确保这会导致错误?

【问题讨论】:

  • 它是否真的将sortQuery 推断为对象类型?还是有?
  • 这里也是一样,可能是因为函数是 JavaScript 中的对象:typescriptlang.org/play/…
  • 与此结果相同:find: (collection: string, query: object, sortQuery: object, cb?: Function) => { socketManager.call('find', collection, query, sortQuery, cb); }
  • 有什么方法可以确保“函数”类型而不是对象?
  • 你知道sortQuery的参数和返回类型吗?你可以更具体地定义它

标签: javascript typescript


【解决方案1】:

使用 TypeScript Conditionals (TS v2.8),我们可以使用 Excludeobject 类型中排除 Functions 使用 Exclude<T, Function>

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 会引发错误,但不会引发后两次调用find

然后显示的错误如下:

  • [ts] '(a, b) => void' 类型的参数不可分配给'never' 类型的参数。 [2345]
  • [ts] '"I am a string"' 类型的参数不能分配给 'object' 类型的参数。 [2345]

【讨论】:

  • 这是一个很好的解决方案。所以我没有提到的一件事是我有一个脚本,它在我的所有服务器函数中运行并解析文件以获取函数名称和参数以在客户端生成函数。如果我更改脚本以在所有功能上执行此操作可以吗?
  • find: (collection: string, query: object, sortQuery: Exclude = {}, cb?: Function) => { socketManager.call ('查找', 集合, 查询, 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); },
【解决方案2】:

对象和函数本质上是一回事,但类型有助于我们区分它们的功能。

考虑一下:

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

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

【讨论】:

  • 这也是使用对象的一个​​很好的解决方案。不幸的是,这将花费大量时间来重新处理所有调用/创建一个脚本来解决所有这些问题。不过答案很好。
  • 宁可使用析构参数fnName( { p1, p2, p3 } : { p1: string, p2: number, p3: boolean })
猜你喜欢
  • 2020-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-16
  • 2018-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多