xingbofeng / xingbofeng.github.io

counterxing的博客
https://xingbofeng.github.io
175 stars 18 forks source link

别在props中使用bind #4

Open xingbofeng opened 7 years ago

xingbofeng commented 7 years ago

原文链接:Don't Use Bind When Passing Props

如今我们在编写React组件时经常会遇到通过props传递一个函数的情况,通常父组件向子组件传递一个回调函数,子组件事件触发这个回调函数可以改变父组件的状态。

因而,维持这个回调函数的this指向便显得十分重要。

以下有许多方法能保证this能准确挂载于组件上。毫无疑问,一些方法是较为优秀的,我们推荐使用这些方式来传递this

自动绑定(good, 只能通过 React.createClass)

当你使用React.createClass创建你的组件时,你的组件上的所有方法都会被自动地绑定在你的组件作用域内。即便不使用bind你也可以自由准确地在组件中传递你的回调。

var Button = React.createClass({
  handleClick: function() {
    console.log('clickity');
  },
  render: function() {
    return (
      <button onClick={this.handleClick}/>
    );
  }
});

在render中使用bind(bad, ES6)

当我们使用ES6class语法时,React不能够自动地将其成员函数绑定到组件上。

像下面这个例子:在传递回调函数的最后时刻绑定this是一种能让你的应用正常工作的方式之一。但是,这样做会导致每次组件重新渲染(re-render)时都会创建一个新的函数,因而会使得你的应用性能有明显的降低。

其实,这个问题并不是说每次创建函数时都会产生较大的性能消耗。它指的是每时每刻通过bind创建的函数都会通过props传递一个同样功能的新函数到你的子组件。当这种情况发生时,子组件上的shouldComponentUpdate便会反复触发,因而产生大量的性能消耗,直至你的应用崩溃。

class Button extends React.Component {
  handleClick() {
    console.log('clickity');
  }

  render() {
    return (
      <button onClick={this.handleClick.bind(this)}/>
    );
  }
}

以下这种绑定this的方式也会导致你在创建新函数的时候render被反复触发。

class Button extends React.Component {
  handleClick() {
    console.log('clickity');
  }

  render() {
    var handleClick = this.handleClick.bind(this);
    return (
      <button onClick={handleClick}/>
    );
  }
}

在render中使用箭头函数(bad, ES6)

与上面的例子类似,如果你期望使用箭头函数代替使用bind去绑定组件作用域,也同样会导致你在创建新函数的时候render被反复触发。虽然这种方式看起来很棒,然而,并不推荐。

class Button extends React.Component {
  handleClick() {
    console.log('clickity');
  }

  render() {
    return (
      <button onClick={() => this.handleClick()}/>
    );
  }
}

在Class实例作用域中使用箭头函数(good, ES8+)

在组件创建时,我们通过声明一个实例方法handleClick。而在render和其它函数组件方法中,使用this.handleClick调用这个方法。此时我们并不用担心this绑定的问题,因为箭头函数的语法糖,其已经自动绑定this

因为从ES标准上来说,这种方式已经不属于ES6ES7的范畴了。因而被贴上了ES8+的标签。随着ES2016标准(其中主要包含Array.prototype.includes以及求幂操作符)的最终定稿,即便未来这样的语法会加入标准,那也应该被称作ES2017(ES8)及其后的版本了。

虽然这种语法已经被Babel支持,但使用这样的尚未进入ES标准的语法仍旧存在一定风险。即便如此,许多人仍然在使用这种语法,让它看起来已经被内定入ES标准的样子。

class Button extends React.Component {
  // Use an arrow function here:
  handleClick = () => {
    console.log('clickity');
  }

  render() {
    return (
      <button onClick={this.handleClick}/>
    );
  }
}

通过构造函数绑定this(good, ES6)

你可以使用构造函数绑定this,此后你便不用担心其指向的问题了。但注意:不要忘记super(props)

class Button extends React.Component {
  constructor(props) {
    super(props);

    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log('clickity');
  }

  render() {
    return (
      <button onClick={this.handleClick}/>
    );
  }
}

使用修饰器语法(good, ES8+)

可以使用叫做autobind-decorator 的库。

import autobind from 'autobind-decorator';

class Button extends React.Component {
  @autobind
  handleClick() {
    console.log('clickity');
  }

  render() {
    return (
      <button onClick={this.handleClick}/>
    );
  }
}

@autobind修饰器将handleClick方法自动绑定上了this。如果你嫌这样做都不过瘾,你甚至可以在Class入口处使用它。

import autobind from 'autobind-decorator';

@autobind
class Button extends React.Component {
  handleClick() {
    console.log('clickity');
  }

  handleOtherStuff() {
    console.log('also bound');
  }

  render() {
    return (
      <button onClick={this.handleClick}/>
    );
  }
}

然而,ES2016/ES2017依然没有把这种语法列入标准。即便这种语法已经被Babel所支持,但现在使用这种语法仍旧存在一定风险。

勿要使用bind传递参数

在回调函数调用的时候,给回调函数预设参数显得十分普遍,特别是在数组遍历中,看下面这个例子:

var List = React.createClass({
  render() {
    let { handleClick } = this.props;
    return (
      <ul>
        {this.props.items.map(item =>
          <li key={item.id} onClick={handleClick.bind(this, item.id)}>
            {item.name}
          </li>
        )}
      </ul>
    );
  }
});

在这里解释一下,避免在render中使用bind,你可以使用这样一个小技巧。

var List = React.createClass({
  render() {
    let { handleClick } = this.props;
    // handleClick still expects an id, but we don't need to worry
    // about that here. Just pass the function itself and ListItem
    // will call it with the id.
    return (
      <ul>
        {this.props.items.map(item =>
          <ListItem key={item.id} item={item} onItemClick={handleClick} />
        )}
      </ul>
    );
  }
});

var ListItem = React.createClass({
  render() {
    // Don't need a bind here, since it's just calling
    // our own click handler
    return (
      <li onClick={this.handleClick}>
        {this.props.item.name}
      </li>
    );
  },

  handleClick() {
    // Our click handler knows the item's id, so it
    // can just pass it along.
    this.props.onItemClick(this.props.item.id);
  }
});