Реагировать.js ES6 избегайте привязки "это" к каждому методу


недавно я начал возиться с React.js и мне это нравится. Я начал с обычного ES5, чтобы получить представление о вещах, все документы написаны на ES5...

но теперь я хотел попробовать ES6, потому что он блестящий и новый, и это, кажется, упрощает некоторые вещи. Что меня очень беспокоит, так это то, что для каждого метода, который я добавил в свои классы компонентов, мне теперь нужно привязать "это", иначе он не работает. Так что мой конструктор в конечном итоге выглядит так это:

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, заключалась в том, чтобы сделать мой код более кратким, но это делает обратное. Любые предложения или вклад будут оценены.

7 56

7 ответов:

можно использовать поля класс для выполнения привязки вне конструктора. Они выглядят следующим образом:

class Foo extends React.Component {

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

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

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

}

поля класса экспериментально поддерживаются Babel через его свойства класса transform, но они все еще "экспериментальные", потому что они 3 Этап Драфт (еще не в предустановке Babel).

вам нужно будет сделать привязку вручную до ES7 или до включения функции в Babel, однако. Эта тема кратко описано в блоге Бабеля на реагировать на ES6+.

Другой альтернативой является использование декораторов. Вы объявляете геттер на прототипе, и при первом доступе к экземпляру он определяет собственное свойство с привязанной версией этой функции.

но есть загвоздка! В разработке он не будет заменять свойство, он будет привязываться к каждому доступу. Это означает, что вы не нарушаете 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);

предложение 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()); }
    }

вы можете добавить любое количество методов к этому.связующее ('method1', 'method2',...)

Если вы используете stage-0 существует синтаксис привязки функции.

class MyComp extends Component {

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

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

}

это разрушает до this.handleClick.call(this), который я думаю, как правило, достаточно эффективен.

одна идея, чтобы избежать привязки

class MyComp extends Component {

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

}

отказ от ответственности: непроверенный, также, не может легко обрабатывать более одного аргумента (в этом случае есть один, событие (e).

кроме того, это ответ, вероятно, пример того, что не чтобы сделать, согласно этой статье, которая, вероятно, стоит прочитать:

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

Я на самом деле предпочитаю имитировать наследование ООП, передавая детям Родительский контекст.

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, Джон

Я создал метод для организации всех "привязок".

class MyClass {
  constructor() {

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

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

  ...
}