admin管理员组

文章数量:1136510

Recently, I've started tinkering with React.js and I love it. I started out in the regular ES5, so as to get the hang of things, the docs are all written in ES5...

But now I wanted to try ES6, because it's shiny and new, and it does seem to simplify some things. What bothers me a lot is that for every method I had added into my component classes I now have to bind 'this' to, otherwise it doesn't work. So my constructor ends up looking like 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);
}

If I were to add even more methods to my class, this would become an even bigger, uglier mess.

My question is, is there some way to get around this, or at least make it easier, shorter and less ugly? One of the main reasons I wanted to try React with ES6 was to make my code more concise, but this is doing the opposite. Any suggestions or input would be appreciated.

Recently, I've started tinkering with React.js and I love it. I started out in the regular ES5, so as to get the hang of things, the docs are all written in ES5...

But now I wanted to try ES6, because it's shiny and new, and it does seem to simplify some things. What bothers me a lot is that for every method I had added into my component classes I now have to bind 'this' to, otherwise it doesn't work. So my constructor ends up looking like 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);
}

If I were to add even more methods to my class, this would become an even bigger, uglier mess.

My question is, is there some way to get around this, or at least make it easier, shorter and less ugly? One of the main reasons I wanted to try React with ES6 was to make my code more concise, but this is doing the opposite. Any suggestions or input would be appreciated.

Share Improve this question edited Dec 25, 2015 at 9:17 Kirill Fuchs 13.7k4 gold badges43 silver badges73 bronze badges asked Aug 24, 2015 at 22:20 PavlinPavlin 5,5287 gold badges43 silver badges56 bronze badges 12
  • 1 Are you really using all of your methods as handlers somewhere? – Bergi Commented Aug 24, 2015 at 22:33
  • See also this answer – Bergi Commented Aug 24, 2015 at 22:35
  • Well this was taken from a higher level component, so yes, they are all being used somewhere down the hierarchy. I tried to follow their philosophy and tried to best figure out which component needs to know what, and this was what I ended up with. – Pavlin Commented Aug 24, 2015 at 22:36
  • But aren't (shouldn't) they be called as methods on the component object, instead of being passed around as functions? – Bergi Commented Aug 24, 2015 at 22:38
  • 2 I personally use decorators (specifically autobind) for this instead of class property initializers. – Michelle Tilley Commented Aug 25, 2015 at 4:13
 |  Show 7 more comments

9 Answers 9

Reset to default 75

You can use class fields to do the binding outside the constructor. They look like the following:

class Foo extends React.Component {

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

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

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

}

Class fields are supported experimentally by Babel via its class properties transform, but they are still "experimental" because they are a Stage 3 Draft (not yet in a Babel preset).

You will need to do the binding manually until ES7 or until enabling the feature in Babel, however. This topic is covered briefly in Babel's blog post on React on ES6+.

Another alternative is to use decorators. You declare a getter on the prototype, and on first access for an instance it defines an own property with a bound version of that function.

But there's a catch! In development it won't replace the property, it'll bind on every access. This means you don't break react-hot-loader. At least for me, that's pretty important.

I created a library, class-bind, that provides this.

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>
    );
  }
}

Decorators too unstable for you? You can bind everything or some things with the same benefits.

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

bind(App.prototype, 'returnsFoo');

// or
bindAll(App.prototype);

I created a method to organize all the "binds".

class MyClass {
  constructor() {

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

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

  ...
}

Ssorallen's suggestion is great but if you want another way there is:

    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()); }
    }

You can add any number of methods to this.binder('method1', 'method2', ...)

If you use stage-0 there is a function binding syntax.

class MyComp extends Component {

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

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

}

This destructures to this.handleClick.call(this), which I think is generally performant enough.

One idea to avoid bind

class MyComp extends Component {

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

}

disclaimer: untested, also, cannot easily handle more than one argument (in this case, there is one, event (e).

Also, this is answer is probably an example of what not to do, according to this article which is probably a worthwhile read:

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

I actually prefer to imitate OOP inheritance by passing children the parent context.

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, Jon

I use a helper function doBinding(this), which I call in each constructor. In this example it binds _handleChange1() and _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() {
       ...
    }
}

The method works even if you are not using Babel.

My handler methods all begin with _ (a convention to indicate they are private). So doBinding() looks for the _. You can remove the if (key.startsWith("_")) if you don't use this convention.

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

How about using a common function to do the binding-work like this:

// 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){ 
                //...
              }
          })
     }
}

本文标签: javascriptReactjs ES6 avoid binding 39this39 to every methodStack Overflow