目录
介绍
我对TypeScript和React的介绍性演示引发了关于在React和DOM中键入事件处理程序的热烈讨论。第一个是相当简单的:React中的事件或多或少与您期望的完全相同。然而,TypeScript对DOM事件的古怪实现导致了一个有趣的对话,关于期望,权衡以及尝试键入像JavaScript这样快速松散的动态语言的挑战。
但首先,React
React事件处理程序接收合成的React特定事件。一个onClick处理程序,例如,将可预期通过相应的React.MouseEvent传递:
class MyComponent extends React.Component<P, S> {
// See: @types/react/index.d.ts
onClick(e: React.MouseEvent) {
// ...
}
render() {
return (
<button onClick={this.onClick}>
Click me!
</button>
);
}
}
如果我们检查相应的类型声明,我们将看到React为事件处理程序提供了一个相当具体的类型,如onClick:
// @types/react/index.d.ts
interface MouseEvent<T = Element> extends SyntheticEvent<T> {
// ...
}
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
interface DOMAttributes<T> {
// ...
onClick?: MouseEventHandler<T>;
}
由于点击需要一个鼠标,期望onClick应该接收一个MouseEvent(而不是说,一个KeyboardEvent)是有道理的。用其他任何东西调用onClick都会产生一个编译器错误,并且——如果我们只关心React——那就是这个错误。
DOM怎么样?
将React视为文档对象模型(DOM)之上的“一次编写,随处运行”层。开发人员可以使用与供应商无关的一致的全局视图,而不是处理古怪的浏览器API。而任何以其他方式完成它的人都知道,这是一个非常了不起的事情。
但有时候,我们必须切断React的友好抽象并处理下面的内容。在这里,事件处理变得更有趣。设置很简单:
const onClick = (e: MouseEvent) =>
console.log(`(${e.clientX}, ${e.clientY})`);
window.addEventListener('click', onClick);
但是这里有一个令人惊讶的实现,值得额外关注。
旁白:为什么要Type?
对TypeScript(或任何其他静态类型检查器)的投资可以保证我们程序中的数据格式正确。关于这些保证的任何问题都会进一步产生关于我们投资价值的令人不安的问题。我们希望TypeScript在问题发生时告诉我们。
我敢打赌,你看到它的发展方向。
邪恶的东西
我们来试试吧。除了点击鼠标,我们现在还让用户用键“点击”。
const onClick = (e: MouseEvent) =>
console.log(`(${e.clientX}, ${e.clientY})`);
window.addEventListener('click', onClick);
window.addEventListener('keydown', onClick);
正如人们所预料的那样,keydown会产生一个KeyboardEvent——而MouseEvent不是回调所期望的。然而这个变化编译了!虽然控制台输出不是特别有用,但它也会运行:
> (undefined, undefined)
编译器无法在编译时知道传递一个KeyboardEvent到onClick是安全的——但由于某种原因,它仍然允许该行为。
在我的类型系统?
类型系统是根据健全性来描述的——声音类型系统是捕获所有错误的系统,无论它们是否在运行时都是可能的——以及完整性——完整的系统只能报告可能的错误。理想的系统完善无缺。
TypeScript的贡献者是聪明的人,毫无疑问他们非常清楚这一点。然而,我们有确凿的证据表明TypeScript行为不当。这也不是唯一的情况:编译器会忽略许多特殊情况。
这些情况非常慎重。TypeScript的设计目标之一是保持JavaScript的超集。这意味着允许JavaScript允许的大多数行为,从广义上讲,这是非常多。
其中一个问题就是回调重用:一种不寻常但并非罕见的模式,TypeScript不再适应(flowtype,顺便说一句,没有)。感觉像将鼠标事件和键盘事件绑定到同一个处理程序?这是你的规则。
根据可扩展Web的精神,React可以制定自己的规则。其中一条规则是一致的回调行为。
结论
但是,即使TypeScript正在按照JavaScript规则处理,编译器也会允许这样做。让我们尝试调整onClick以期待一个数字:
const onClick = (n: number) => { /* ... */ }
window.addEventListener('click', onClick);
这一次,我们将看到预期的编译器错误:
// Argument of type '(e: number) => void' is not assignable to
// parameter of type 'EventListenerOrEventListenerObject'.
// Type '(e: number) => void' is not assignable to type 'EventListenerObject'.
// Property 'handleEvent' is missing in type '(e: number) => void'.
不管怎样,一个KeyboardEvent,MouseEvent应该在那里。但是,即使在适应浏览器API时,number也是一个过桥。即使是不健全的案件也有其局限性。
原文地址:https://www.codeproject.com/Articles/1277219/TypeScript-Event-Handlers