javascript - 流联合类型在递归调用中不起作用
问题描述
我在对自定义联合类型进行类型检查时遇到问题,该类型允许一个Function
,Promise
或者Array
包含false
一个类的一个或一个实例Plugin
。
该代码无需类型检查即可工作,并允许开发人员将 a Plugin
or嵌套false
在任何其他允许的类型中,但是,我似乎无法让类型系统允许代码没有任何错误。
我正在假设我们可以使用递归类型,例如type T = false | Array<T>
,该类型可以是 false 或包含另一种T
类型的数组。我在 github 评论中读到递归类型或允许,但是找不到代码示例。
// @flow
type object = {}
type Plugins =
false
| Plugin
| () => Plugins
| Array<Plugins>
| Promise<Plugins>
class Plugin {
name: string
constructor(name) {
this.name = name
}
}
class Engine {
params = {}
plugins = {}
constructor(params: object) {
this.params = params
}
pipe(plugin: Plugins): Engine {
// do nothing if plugins is false
if (plugin === false) {
return this
}
else if (typeof plugin === 'function') {
this.pipe(plugin())
}
// get plugin from inside a Promise
else if (plugin instanceof Promise) {
plugin.then(plugin => this.pipe(plugin))
}
// iterate over each item of an array
else if (Array.isArray(plugin)) {
plugin.forEach(plugin => this.pipe(plugin))
}
// initialize each plugin
else if (plugin instanceof Plugin) {
this.plugins[plugin.name] = plugin
}
// return this for chaining
return this
}
}
const engine = new Engine({ name: 'one'})
.pipe(new Plugin('plugin-one'))
.pipe([new Plugin('plugin-two')])
.pipe(Promise.resolve([new Plugin('plugin-three')]))
.pipe(() => new Plugin('plugin-four'))
// should cause a flow type error
.pipe(true)
// I would like this to work deeply nested in any acceptable type
.pipe([() => Promise.resolve([() => [Promise.resolve([new Plugin('plugin-five')])]])])
setTimeout(function() {
console.log(engine)
}, 20)
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>
我尝试了多种不同的类型定义,并取得了不同程度的成功,但是,它们通常会修复警告,但不再捕获类型错误。
这是试图将最终类型与包装类型分开
type PluginTypes = false | Plugin
type Plugins =
Array<PluginTypes>
| Promise<PluginTypes>
| () => PluginTypes
这将通过添加Plugins
到联合类型来防止错误,但是,类型检查似乎不再起作用。
type Plugins =
false
| Plugin
| Plugins
| Array<Plugins>
| Promise<Plugins>
| () => Plugins
谢谢你的帮助
解决方案
优先级问题
如果您通过Prettier代码格式化程序运行类型定义,您可能会发现一个错误:
type Plugins =
| false
| Plugin
| (() => Plugins | Array<Plugins> | Promise<Plugins>)
如您所见,您的顶级Plugins
类型不会接受 anArray
或 a Promise
,因为返回值Plugins
的优先级高于 function () => Plugins
。您可以通过在函数类型周围添加括号来解决此问题:() => Plugins
type Plugins =
| false
| Plugin
| (() => Plugins)
| Array<Plugins>
| Promise<Plugins>
对象构造导致的流程错误any
这是向前迈出的一步,但是当您将此更改应用于原始代码时,您会看到.pipe(true)
现在无法抛出所需的错误。使用 Try Flow 的特性来识别光标下元素的类型,我通过在底部附近添加一些测试代码找到了为什么会发生这种情况:
const initialEngine = new Engine({ name: "one" });
// In Try Flow, put the cursor within `initialEngine`. The type is `any`.
new Engine(…)
返回一个类型的值,any
而不是Engine
你期望的类型。这any
就是为什么没有调用.pipe
之后会抛出错误的原因,即使它们应该如此。
您可以通过使用类型注释或变量赋值手动将构造的引擎注释为来解决此问题:Engine
console.log(
(new Engine({ name: 'one'}): Engine)
.pipe(new Plugin('plugin-one'))
// …
)
const initialEngine: Engine = new Engine({ name: 'one'})
console.log(
initialEngine
.pipe(new Plugin('plugin-one'))
// …
)
理想情况下,您可以new Engine(…)
返回一个Engine
,但我认为这是 Flow 中的一个错误,而不是通过更改代码来解决的问题。我的证据是,在您的方法中删除以下代码pipe
也可以解决问题:
// get plugin from inside a Promise
else if (plugin instanceof Promise) {
plugin.then(plugin => this.pipe(plugin))
}
我在 Flow 的 bug tracker 上创建了一个关于此的问题,用一个更简单的示例演示了这个问题。这个错误需要一个奇怪的特定因素组合来触发;你遇到他们只是你的运气。
在修复错误之前,您应该使用添加类型注释的解决方法。
编辑:更好的解决方法
正如GitHub 问题中提到的wchargin ,您还可以通过将's显式注释为返回来解决此问题:Engine
constructor
void
class Engine {
// …
constructor(params: object): void {
this.params = params
}
// …
}
如果这样做,您将不必在构造对象Engine
的每个地方添加注释。Engine
最终代码
结合这两个修复以获得最终的工作代码(在 Try Flow 中):
// @flow
type object = {}
type Plugins =
false
| Plugin
| (() => Plugins)
| Array<Plugins>
| Promise<Plugins>
class Plugin {
name: string
constructor(name) {
this.name = name
}
}
class Engine {
params = {}
plugins = {}
// type the constructor as returning `void` to work around
// https://github.com/facebook/flow/issues/6738
constructor(params: object): void {
this.params = params
}
pipe(plugin: Plugins): Engine {
// do nothing if plugins is false
if (plugin === false) {
return this
}
else if (typeof plugin === 'function') {
this.pipe(plugin())
}
// get plugin from inside a Promise
else if (plugin instanceof Promise) {
plugin.then(plugin => this.pipe(plugin))
}
// iterate over each item of an array
else if (Array.isArray(plugin)) {
plugin.forEach(plugin => this.pipe(plugin))
}
// initialize each plugin
else if (plugin instanceof Plugin) {
this.plugins[plugin.name] = plugin
}
// return this for chaining
return this
}
}
console.log(
new Engine({ name: "one" })
.pipe(new Plugin('plugin-one'))
.pipe([new Plugin('plugin-two')])
.pipe(Promise.resolve([new Plugin('plugin-three')]))
.pipe(() => new Plugin('plugin-four'))
// should cause a Flow type error
// $ExpectError
.pipe(true)
// The following deeply-nested parameter now works
.pipe([() => Promise.resolve([() => [Promise.resolve([new Plugin('plugin-five')])]])])
)
推荐阅读
- html - 如何让我的页脚响应而不会出现故障?
- html - bootstrap3 中的内联日期选择器
- c# - WPF 中的 Application.OpenForms[0].InvokeRequired 等效项
- javascript - 如何通过 reactnavigation v5 将参数从一个屏幕传递到另一个屏幕
- javascript - `for/in` 循环包含奇怪的索引
- spring-boot - org.hibernate.boot.model.TypeContributor:未找到提供程序
- awk - 如何通过awk排除与列中正则表达式模式匹配的行?
- python - Python 帮助处理包和模块
- javascript - 并行使用 axios 进行 post call
- mysql - 如何在 BelongsToMany 连接表上的 Bookshelf/Knex 中查询?