一种在React中处理事件的新方法
- “Property Initializer Syntax”* 听起来比实际更花哨。 在这个简短的教程中,了解这种编写事件处理程序的替代方法将如何帮助消除
constructor
中的样板文件,并防止渲染中不必要的内存使用。
在 Facebook 文档中,您将看到这样完成的事件处理:
// option 1 class Thing extends React.Component { constructor() { this.handleSmthng = this.handleSmthng.bind(this) } render() { <input onChange={this.handleSmthng}/> } handleSmthng(e) { // ... } }
ES6 类不会自动将 this
范围赋予 handleSmthng
,并且由于您通常想要调用 this.setState
或者可能调用组件中的另一个方法,因此“官方”约定是在构造函数中始终绑定所有事件处理程序。 这可行,但很快就会感觉像样板代码。
// option 2 class Thing extends React.Component { render() { <button onClick={() => this.handleSmthng('foo')}> ADD </button> } handleSmthng(arg1) { // ... } }
这种模式似乎在 React 在线教程中越来越流行。 它将 this
上下文传递给 handleSmthng
并避免构造函数中的样板代码。 哎呀,这个组件中没有状态,所以你甚至不需要构造函数! 我认为这种方法的动机是正确的……但是会有一点性能成本。
使用箭头函数总是会在 JavaScript 中创建一个新的引用,这反过来又会增加应用程序的内存使用量。 虽然 JavaScript 中的内存很便宜,但 React 中的渲染成本很高。 当您将箭头函数传递给子组件时,您的子组件将不加选择地重新渲染,因为(就它而言)该箭头函数是新数据。 这可能意味着大型 React 应用程序获得 60fps 或 50fps 之间的差异。
“……但是,如果此回调作为道具传递给较低的组件,则这些组件可能会进行额外的重新渲染。” 反应文档
二结一闭
有一种更简洁的方法来编写事件处理程序,它 1) 避免样板文件和 2) 不会导致额外的重新渲染: 属性初始化语法 ! 这是一个花哨的名字,但想法很简单……只需使用箭头函数来定义您的事件处理程序。 像这样:
class TodoApp extends React.Component { render() { return ( <div> <button onClick={this.handleClick}> ADD </button> <input onChange={this.handleInput}/> </div> ); } handleClick = () => { // "this" } handleInput = (e) => { // "this", "e" } }
您定义了两个处理程序,它看起来非常好。 没有样板。 易于阅读。 易于重构……如果您想传递参数:
class TodoApp extends React.Component { render() { return ( <div> <button onClick={this.handleClick}> ADD </button> <input onChange={this.handleInput}/> { this.state.todos.map((item) => { return ( <li onClick={this.handleRemove(item.id)}> {item.text} </li> ); }); } </div> ) } handleClick = () => { // "this" } handleInput = (e) => { // "this", "e" } handleRemove = (id) => (e) => { // "this", "e", "id" } }
繁荣! 您可以在渲染方法或构造函数中不使用 bind
来传递参数! 一切看起来都非常整洁。
如果你不习惯看到箭头函数,这可能看起来很奇怪。 请记住,在 ES6 中,粗箭头语法允许 省略花括号 用于单行语句……它会隐含地 return
在那一行! 这就是 Babel 转换 handleRemove
的方式:
handleRemove: function (item) { return function (e) { // "item" AND "e" 🌈 } }
要将 Babel 配置为使用 Property Initializer Syntax,请确保您已安装 “transform-class-properties” 插件或 enable stage-2。 如果你正在使用“create-react-app”……它已经存在了!
有一个 ESLint 规则 会告诉你不要在渲染中使用“绑定”或箭头函数
使用属性初始化器语法的好处
我最好的猜测是 Facebook 没有在他们的文档中“正式”认可这种模式是因为第二阶段 ES6 还没有最终确定,所以 Property Initializers 仍然被认为是非标准的。 然而,create-react-app
生成器已经启用了第 2 阶段,所以……很可能在不久的将来,Property Initializers 将成为定义事件处理程序的事实。
一旦您熟悉了 Property Initializers 并开始使用它们来定义处理程序方法,您将获得两个显着的好处:
- 编写更少的样板 不必在构造函数中编写
bind
语句是相当不错的。 现在你只需定义方法——就是这样✨。 如果您需要传递参数,只需使用单个闭包进行包装,并记住在render
中调用该处理函数。 作为一个额外的好处,如果您需要在其他地方重构您的事件处理程序,您只有一个可以剪切粘贴的地方。 - 降低内存使用量 在
render
中使用箭头函数是一个坏主意,因为“设计”渲染在组件的生命周期中会大量发生; 为每个箭头函数分配一个新指针。 弃权render
中的箭头函数将确保您在节食时保持组件的内存使用。
查看此 CodePen 以查看 Property Initializer Syntax 的实际应用