一种在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 的实际应用