web-dev-qa-db-ja.com

React.js ES6は、「this」をすべてのメソッドにバインドしないようにします

最近、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);
}

クラスにさらにメソッドを追加すると、これはさらに大きくて見苦しい混乱になります。

私の質問は、これを回避する方法はありますか、少なくともそれをより簡単に、短く、andくしませんか? React ES6で試したかった主な理由の1つは、コードをより簡潔にすることでしたが、これは反対です。提案や入力をいただければ幸いです。

63
Pavlin

class fields を使用して、コンストラクターの外側でバインディングを行うことができます。次のようになります。

class Foo extends React.Component {

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

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

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

}

クラスフィールドは クラスプロパティtransform を介して実験的にサポートされていますが、 ステージ3ドラフト (まだバベルプリセットではない)であるため、「実験的」です。

ただし、ES7まで、またはBabelで機能を有効にするまで、手動でバインドを行う必要があります。 React on ES6 + に関するBabelのブログ投稿で、このトピックについて簡単に説明しています。

55
Ross Allen

別の方法は、デコレータを使用することです。プロトタイプでゲッターを宣言し、インスタンスへの最初のアクセスで、その関数のバインドされたバージョンで独自のプロパティを定義します。

しかし、キャッチがあります!開発では、プロパティは置き換えられず、すべてのアクセスでバインドされます。 これは、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);
11
Brigand

バインドを回避する1つのアイデア

class MyComp extends Component {

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

}

免責事項:テストされていないため、複数の引数を簡単に処理できません(この場合、イベント(e)が1つあります)。

また、これはおそらく価値のあるこの記事によると、おそらくnotの例です。

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

1
Alexander Mills

すべての「バインド」を整理するメソッドを作成しました。

class MyClass {
  constructor() {

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

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

  ...
}
1
Pablo Darde

ソラレンの提案は素晴らしいですが、別の方法が必要な場合は次のとおりです。

    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'、...)に任意の数のメソッドを追加できます

1
J. Mark Stevens

私は実際に、子に親コンテキストを渡すことで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
Jon Pellant

_stage-0_を使用する場合、関数バインディング構文があります。

_class MyComp extends Component {

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

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

}
_

これはthis.handleClick.call(this)に分解されます。これは一般に十分なパフォーマンスがあると思います。

1
Jon Jaques

ヘルパー関数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
James