React.js ES6避免将'this'绑定到每个方法


73

最近,我开始修补React.js,我喜欢它。我开始使用常规的ES5,以便掌握所有内容,所有文档均使用ES5编写...

但是现在我想尝试一下ES6,因为它有光泽而且是新的,而且似乎确实简化了一些事情。让我感到困扰的是,对于我添加到组件类中的每个方法,现在都必须将“ this”绑定到该方法,否则它将无法正常工作。所以我的构造函数最终看起来像这样:

constructor(props) {
  super(props);
  this.state = { ...some initial state... }

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

如果我要在类中添加更多方法,这将变得更大,更难看。

我的问题是,是否有某种方法可以解决此问题,或者至少使其更容易,更短且更不丑陋?我想在React中使用ES6的主要原因之一是使我的代码更简洁,但这却相反。任何建议或意见将不胜感激。


1
您是否真的在某个地方使用所有方法作为处理程序?
Bergi

另请参阅此答案
Bergi 2015年

好吧,这是从更高级别的组件中提取的,因此,是的,它们都在层次结构中的某个地方使用。我试图遵循他们的哲学,并试图最好地弄清楚哪个组件需要知道什么,而这就是我最终得到的结果。
帕夫林

但是,不是(不应该)将它们作为组件对象上的方法调用,而不是作为函数来传递吗?
Bergi

2
我个人使用装饰器(特别是autobind)来代替类属性初始化器。
米歇尔·蒂里

Answers:


65

您可以使用类字段在构造函数外部进行绑定。它们如下所示:

class Foo extends React.Component {

  handleBar = () => {
    console.log('neat');
  };

  handleFoo = () => {
    console.log('cool');
  };

  render() {
    return (
      <div
        onClick={this.handleBar}
        onMouseOver={this.handleFoo}
      />
    );
  }

}

Babel通过其类属性转换实验性地支持了类字段,但是它们仍然是“实验性的”,因为它们是Stage 3 Draft(尚未在Babel预设中)。

您将需要手动进行绑定,直到ES7或启用Babel中的功能为止。Babel在React on ES6 +上的博客文章对此主题进行了简要介绍。


谢谢。真可惜 我使用gulp作为构建系统,似乎gulp软件包没有该选项。关于如何使它更容易忍受的任何建议?
帕夫林

1
哦,是的,可以。我随babel({ stage: 0 })它一起运行,并且奏效了。这似乎是一个临时解决方案,因为它尚处于早期阶段,但目前效果很好。感谢您的帮助。
帕夫林

4
@Brandon它们具有相同的效果。constructor每次创建新实例时都会调用该方法,因此bind每个实例都会进行调用。React docs建议避免bindrender函数中调用,因为render在给定实例上调用了多次,而构造函数恰好被调用了一次。
罗斯·艾伦

2
@DivyanshuMaithani类属性初始值设定项目前仍是一个建议,因此无法确定本机版本是否会影响性能。但是,如果您使用的是Babel,则将属性初始值设定项转换为在构造函数中分配绑定实例。他们是完全一样的:babeljs.io/repl/...
罗斯-阿伦

1
这个例子真的很有帮助,这种方法也使组件看起来更干净,可以使用它:)
Divyanshu Maithani

12

另一种选择是使用装饰器。您在原型上声明了一个吸气剂,并在实例的首次访问中定义了带有该函数绑定版本的自己的属性。

但是有一个陷阱!在开发中,它不会替换该属性,它将在每次访问时绑定。这意味着您不会破坏react-hot-loader。至少对我来说,这很重要。

我创建了一个库class-bind来提供此功能。

import {bound} from 'class-bind';

class App {
  constructor(){
    this.foo = 'bar';
  }

  @bound
  returnsFoo(){
    return this.foo;
  }

  render(){
    var returnsFoo = this.returnsFoo;
    return (
      <div>
        {returnsFoo()} === 'bar'
      </div>
    );
  }
}

装饰器对您来说太不稳定了吗?您可以将所有东西或某些东西绑定在一起,获得相同的好处。

import {bind, bindAll} from 'class-bind';

bind(App.prototype, 'returnsFoo');

// or
bindAll(App.prototype);

这看起来很棒。我一定会试一试。我从来没有真正看过热加载器,所以对我来说这不是一个问题,但它确实很有用。谢谢。
帕夫林

1

Ssorallen的建议很好,但是如果您想要另一种方法,则可以:

    class AppCtrlRender extends Component {
        binder(...methods) { methods.forEach( (method) => this[method] = this[method].bind(this) ); }

        render() {
            var isMobile = this.state.appData.isMobile;
            var messages = this.state.appData.messages;
            return (
                <div id='AppCtrlSty' style={AppCtrlSty}>
                    React 1.3 Slider
                    <br/><br/>
                    <div className='FlexBoxWrap'>
                        <Slider isMobile={isMobile}/>
                        <JList data={messages}/>
                    </div>
                </div>
            );
        }
    }

    var getAppState = function() {
        return {
            appData: AppStore.getAppData()
        };
    };

    export default class AppCtrl extends AppCtrlRender {
        constructor() {
            super();
            this.state = getAppState();
            this.binder('appStoreDidChange');
        }

        componentDidMount() {
            var navPlatform = window.navigator.platform;
            Actions.setWindowDefaults(navPlatform);
        }
        componentWillMount() { AppStore.onAny(this.appStoreDidChange); }
        componentWillUnmount() { AppStore.offAny(this.appStoreDidChange); }
        appStoreDidChange() { this.setState(getAppState()); }
    }

您可以向this.binder('method1','method2',...)添加任意数量的方法


谢谢您的建议,这样可以减少绑定方法的痛苦,但是仍然需要我指定要绑定的方法。暂时,ssorallens的建议效果很好,我只是在babel上启用了实验功能。我不知道这将可行多久,但就目前而言,我认为这是最短,最简洁的选择。
帕夫林

我测试了活页夹方法,效果很好。我只是将方法重命名为bindPublicMethods,所以使用它有某种信息上的好处,因为在es6中无法标记公共方法和私有方法。
Trendfischer 2015年

1

如果使用stage-0,则存在函数绑定语法。

class MyComp extends Component {

  handleClick() { console.log('doing things') }

  render() {
    return <button onClick={::this.handleClick}>Do Things</button>
  }

}

这会破坏为this.handleClick.call(this),我认为它通常具有足够的性能。


3
哇,这真是太棒了,但是它处于阶段0以及所有阶段,我可能不建议在严重的情况下使用它。我不知道他们提出了此功能。
巴甫林

绝对可靠的建议,但我承认我不太担心它,可以放心地使用它:D
乔恩·雅克

4
React文档建议不要这样做,因为在render组件的方法中放置一个bind方法将迫使它与每次重新渲染绑定。“我们建议您将事件处理程序绑定到构造函数中,这样对于每个实例它们都只绑定一次来源
Brandon

简单的<button onClick = {()=> this.handleClick()}>做事</ button>怎么样?
tomaszbak

应该有糖<button onClick={() => this.handleClick(arguments)}>
daviestar

1

一种避免束缚的想法

class MyComp extends Component {

  render() {
    return <button onClick={e => this.handleClick(e)}>Do Things</button>
  }

}

免责声明:未经测试,也不能轻易处理一个以上的参数(在这种情况下,有一个事件(e)。

另外,根据这篇文章,答案可能是做某事的一个例子,可能值得一读:

https://daveceddia.com/avoid-bind-when-passing-props/


onClick = {(e, arg1, arg2, arg3) => this.handleClick(e, arg1, arg2, arg3)}将有4个参数。箭头函数可以使用任意数量的参数。
布兰登·杜贝

2
这样,每次调用渲染时都会创建一个新函数
ciekawy

1

我实际上更喜欢通过将子级传递给父级上下文来模仿OOP继承。

class Parent extends Component {
  state = {happy: false}

  changeState(happy) {
    this.setState({happy})
  }

  render() {
    return (
      <Child parent={this} >
    )
  }
}

class Child extends Component {
   //...
   this.props.parent.changeState(true)
}

$ 0.02,乔恩


1

我创建了一种组织所有“绑定”的方法。

class MyClass {
  constructor() {

    this.bindMethods([
      'updateLocationFields',
      'render',
      'loadCities',
    ]);
  }

  bindMethods(methods) {
    methods.forEach((item) => {
      this[item] = this[item].bind(this);
    });
  }

  ...
}

>。>。。。。。。
安德鲁

0

我使用一个辅助函数doBinding(this),在每个构造函数中都调用它。在此示例中,它绑定_handleChange1()_handleChange2()

class NameForm extends React.Component {
    constructor(props) {
        super(props);
        doBinding(this);
        this.state = {value1: "", value2: ""};
    }
    _handleChange1(event) {
        this.setState({value1: event.target.value});
    }
    _handleChange2(event) {
        this.setState({value2: event.target.value});
    }
    render() {
       ...
    }
}

即使您不使用Babel,该方法仍然有效。

我的处理程序方法都以_(表示它们是私有的约定)开头。因此doBinding()寻找_if (key.startsWith("_"))如果不使用此约定,则可以删除。

function doBinding(obj) {
    const proto = Object.getPrototypeOf(obj);
    for (const key of Object.getOwnPropertyNames(proto)) {
        if (key.startsWith("_")) {
            obj[key] = obj[key].bind(obj);
        }
    }
}

0

如何使用通用函数执行如下绑定操作:

// common function:
function bind(self,methods){
     for(var key in methods){
       self[key] = methods[key].bind(self);
     }
}

// your class:
class MyClass {
     constructor() {
          bind(this,{
              someHandler1(event){ 
                //... 
              },
              someHandler2(event){ 
                //...
              }
          })
     }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.