我们将Flow与React结合使用,并编写了这个DropDown包装器,它可以为我们做一些事情。
直到现在,该下拉列表仅需要使用number
id,但是最近,我们还必须添加对string
id的支持。
不想重写所有内容,我们考虑在道具中使用通用类型参数,以便id(和onChange处理程序)将使用该通用类型。
但是,我们似乎无法正确使用该更改处理程序。尝试调用处理程序时,我们会收到关于要发送的值的类型(number
或string
)的Flow错误,告诉我们该类型与T
不兼容:
Cannot call 'handler' with 'value' bound to 'value' because string [1] is incompatible with 'T'
与number
调用相同。
我们相信,如果我们成功检查一个通用值的类型,它将使用我们道具中相同的通用类型来折叠每个元素的类型。他们在这里:
type Props<T : string | number> = {
options?: Array<{id: T, displayValue: string}>,
onValueChange?: (value: T | null) => mixed,
}
因为我们知道Flow不允许使用带有onValueChange
的道具来创建元素,如果number
包含options
的ID,则string
会处理{例如,我们认为通过检查options
中id的类型,我们也可以将T
的{{1}}折叠起来。
这是我们得到错误的地方:
onValueChange
这样,我的问题是:是否可以确定函数参数的泛型,以便我们可以按需使用它而不会出错?
如果答案是肯定的,那么我们的代码在做什么,阻止类型检查按需进行?
如果答案是否定的,还有什么比将我们的值转换为export class DropDown<T : string | number> extends React.Component<Props<T>, {}> {
handleChange = (event: {target: {value: string}}) => {
if (!this.props.onValueChange) return
const handler = this.props.onValueChange
const firstElement = this.props.options && this.props.options[0]
if (!firstElement) return
const value = event.target.value
// I don't understand why, but this if-else if generates an error.
if (typeof firstElement.id === 'string') {
handler(value)
} else if (typeof firstElement.id === 'number') {
const num = parseInt(value)
handler(num)
}
}
}
来使我们的处理程序接受它更好的方法了?
我在此Flow Try snippet中编写了一个最小的工作示例,因此您可以看到实时错误以及预期的用法(请注意,用法部分中的错误是预期的:它们在那里显示使用通用组件就可以按我们希望的方式进行工作,即使使用值也不行)。重要的错误是错误输出中的前两个。
提前感谢您的帮助!
答案 0 :(得分:1)
感谢这个非常有趣的问题! 不幸的是,我认为现在无法通过优化数组中第一个元素的类型来优化类型T。当我们使用以下语句时可以看到这一点:
if (firstElement) {
if (typeof firstElement.id === 'string') {
const refined: string = firstElement.id;
const notRefined: T = refined; // Errors
const alwaysWorks: T = firstElement.id;
}
const alwaysWorks: T = firstElement.id;
}
第二次分配错误。有趣的是,这也可以通过将第一个赋值标记为T
来解决,这表明Flow的工作方式:Flow本身并不精简类型,而是精简值的类型标签。而且我认为您已经正确地得出结论,您需要做的是完善回调函数的值。但这是行不通的,因为我们无法对函数进行任何运行时检查(可能要获取其参数数量除外)。
因此,我们了解到Flow不够好。当您更深入地使用Flow时,您可能会不时注意到这一点:很难表达您想做的事情。我不确定Flow是否出错,或者只是很难键入的JavaScript。无论如何,作为一种解决方案,我建议您仅使用Function
注释处理程序变量。函数在某种程度上可以是任何类型,并使函数更通用。我们知道代码可以工作(请参见我的更正,进行空检查,然后检查id
属性而不是对象),并且外部接口非常明确地说明了类型。有时,重要的是API,在内部,我们不得不做一点修改。很好(例如,我们可以在热门项目like these 13 cases in graphql-js中看到很多东西)
奖金:如果您喜欢函数式编程,请查看this library如何处理类型,以创建Maybe类型的幻觉,而这种幻觉没有运行时开销,因为它实际上只是null
。
答案 1 :(得分:0)
我认为您正在考虑问题。似乎在将event.target.value的类型声明为“字符串”而不是“ T”时犯了一个小错误。
尝试一下
export class DropDown<T : string | number> extends React.Component<Props<T>, {}> {
handleChange = (event: {target: {value: T}}) => { // not {value: string}
if (!this.props.onValueChange) return
const handler = this.props.onValueChange
const value = event.target.value
handler(value)
}
}
我希望这会对您有所帮助。