wake-up-neo.com

React.js ES6 vermeidet die Bindung von 'this' an jede Methode

Vor kurzem habe ich angefangen, mit React.js zu basteln, und ich liebe es. Ich habe mit dem regulären ES5 angefangen, um alles in den Griff zu bekommen, sind die Dokumente alle in ES5 geschrieben ...

Aber jetzt wollte ich ES6 ausprobieren, weil es glänzend und neu ist und einige Dinge zu vereinfachen scheint. Was mich sehr stört, ist, dass ich für jede Methode, die ich in meine Komponentenklassen aufgenommen hatte, jetzt 'this' binden muss, sonst funktioniert es nicht. So sieht mein Konstruktor so aus:

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

Wenn ich meiner Klasse noch mehr Methoden hinzufügen würde, würde dies zu einem noch größeren, hässlicheren Durcheinander werden.

Meine Frage ist, gibt es einen Weg, um dies zu umgehen, oder zumindest einfacher, kürzer und weniger hässlich zu machen? Einer der Hauptgründe, warum ich React mit ES6 ausprobieren wollte, war, meinen Code übersichtlicher zu gestalten, aber das Gegenteil ist der Fall. Anregungen oder Anregungen werden gebeten.

60
Pavlin

Sie können class fields verwenden, um die Bindung außerhalb des Konstruktors durchzuführen. Sie sehen wie folgt aus:

class Foo extends React.Component {

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

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

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

}

Klassenfelder werden von Babel experimentell über ihre class-Eigenschaften transform unterstützt, sie sind jedoch immer noch "experimentell", da sie ein Stage 3 Draft sind (noch nicht in einer Babel-Vorgabe).

Sie müssen die Bindung jedoch manuell bis ES7 oder bis zur Aktivierung der Funktion in Babel vornehmen. Dieses Thema wird in Babels Blogbeitrag über React auf ES6 + kurz behandelt.

51
Ross Allen

Eine andere Alternative ist die Verwendung von Dekorateuren. Sie deklarieren einen Getter für den Prototyp und definieren beim ersten Zugriff für eine Instanz eine eigene Eigenschaft mit einer gebundenen Version dieser Funktion.

Aber da ist ein Fang! In der Entwicklung wird die Eigenschaft nicht ersetzt, sondern wird bei jedem Zugriff gebunden. Dies bedeutet, dass Sie den React-Hot-Loader nicht beschädigen. Zumindest für mich ist das ziemlich wichtig.

Ich habe eine Bibliothek erstellt, class-bind , die dies bereitstellt.

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

Dekorateure zu instabil für Sie? Sie können alles oder einige Dinge mit den gleichen Vorteilen binden.

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

bind(App.prototype, 'returnsFoo');

// or
bindAll(App.prototype);
11
FakeRainBrigand

Eine Idee, die man vermeiden muss

class MyComp extends Component {

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

}

haftungsausschluss: ungeprüft kann auch nicht einfach mehr als ein Argument behandeln (in diesem Fall gibt es eines, event (e)).

Auch diese Antwort ist wahrscheinlich ein Beispiel dafür, was not zu tun ist, gemäß diesem Artikel, der sich wahrscheinlich lohnt:

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

1
Alexander Mills

Ssorallens Vorschlag ist großartig, aber wenn Sie einen anderen Weg wollen, gibt es:

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

Sie können this.binder beliebig viele Methoden hinzufügen ('method1', 'method2', ...)

1
J. Mark Stevens

Wenn Sie stage-0 verwenden, gibt es eine Funktionsbindungssyntax.

class MyComp extends Component {

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

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

}

Dies zerstört die this.handleClick.call(this), die meiner Meinung nach im Allgemeinen performant genug ist.

1
Jon Jaques

Ich habe eine Methode erstellt, um alle "Bindungen" zu organisieren. 

class MyClass {
  constructor() {

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

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

  ...
}
0
Pablo Darde

Eigentlich ziehe ich es vor, OOP Vererbung nachzuahmen, indem ich Kindern den übergeordneten Kontext übergebe.

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

0
Jon Pellant

Ich benutze eine HilfsfunktiondoBinding(this), die ich in jedem Konstruktor aufrufe. In diesem Beispiel bindet es _handleChange1() und _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() {
       ...
    }
}

Die Methode funktioniert auch, wenn Sie Babel nicht verwenden.

Meine Handler-Methoden beginnen alle mit _ (einer Konvention, die angibt, dass sie privat sind). Also sucht doBinding() nach dem _. Sie können if (key.startsWith("_")) entfernen, wenn Sie diese Konvention nicht verwenden.

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