Vor kurzem habe ich angefangen, an React.js zu basteln, und ich liebe es. Ich habe mit dem regulären ES5 angefangen, um den Überblick zu behalten, 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 meinen Komponentenklassen hinzugefügt habe, jetzt 'this' binden muss, sonst funktioniert es nicht. Mein Konstruktor sieht also 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 eine Möglichkeit, dies zu umgehen oder es zumindest einfacher, kürzer und weniger hässlich zu machen? Einer der Hauptgründe, warum ich React with ES6 ausprobieren wollte, war, meinen Code präziser zu gestalten, aber das Gegenteil ist der Fall. Anregungen oder Anregungen wäre dankbar.
quelle
autobind
) Dekorateure anstelle von Klasseneigenschaftsinitialisierern.Antworten:
Sie können Klassenfelder 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 seine Klasseneigenschaftstransformation unterstützt , aber sie sind immer noch "experimentell", da es sich um einen Entwurf der Stufe 3 handelt (noch nicht in einer Babel-Voreinstellung).
Sie müssen die Bindung jedoch manuell bis ES7 oder bis zur Aktivierung der Funktion in Babel durchführen. Dieses Thema wird in Babels Blog-Beitrag zu React on ES6 + kurz behandelt .
quelle
babel({ stage: 0 })
und es hat funktioniert. Dies scheint eine vorübergehende Lösung zu sein, da sie sich in den frühesten Stadien befindet, aber vorerst sehr gut funktionieren wird. Danke für deine Hilfe.constructor
Methode wird jedes Mal aufgerufen, wenn eine neue Instanz erstellt wird. Daher werden diebind
Aufrufe für jede Instanz ausgeführt. In den React-Dokumenten wird empfohlen,bind
Aufrufe in derrender
Funktion zu vermeiden, da dieserender
in einer bestimmten Instanz mehrmals aufgerufen werden, während der Konstruktor genau einmal aufgerufen wird.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 bei jedem Zugriff gebunden. Dies bedeutet, dass Sie den React-Hot-Loader nicht unterbrechen . 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);
quelle
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', ...)
quelle
bindPublicMethods
, sodass sie einen informativen Nutzen bringt, da es in es6 keine Möglichkeit gibt, öffentliche und private Methoden zu markieren.Wenn Sie verwenden,
stage-0
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
this.handleClick.call(this)
, was ich im Allgemeinen für performant genug halte.quelle
render
Methode einer Komponente das Binden bei jedem erneuten Rendern erzwingt. " Wir empfehlen, dass Sie Ihre Ereignishandler im Konstruktor binden, damit sie für jede Instanz nur einmal gebunden werden " Quelle<button onClick={() => this.handleClick(arguments)}>
Eine Idee, um ein Binden zu vermeiden
class MyComp extends Component { render() { return <button onClick={e => this.handleClick(e)}>Do Things</button> } }
Haftungsausschluss: Ungetestet kann auch nicht einfach mehr als ein Argument verarbeiten (in diesem Fall gibt es ein Ereignis (e).
Auch diese Antwort ist wahrscheinlich ein Beispiel dafür, was nicht zu tun ist, laut diesem Artikel, der wahrscheinlich eine lohnende Lektüre ist:
https://daveceddia.com/avoid-bind-when-passing-props/
quelle
onClick = {(e, arg1, arg2, arg3) => this.handleClick(e, arg1, arg2, arg3)}
hätte 4 Argumente. Pfeilfunktionen können beliebig viele Argumente verwenden.Eigentlich ziehe ich es vor, die OOP-Vererbung zu imitieren, indem ich Kindern den Elternkontext ü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, Jon
quelle
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); }); } ... }
quelle
Ich benutze eine Hilfsfunktion
doBinding(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
_
(eine Konvention, die angibt, dass sie privat sind). AlsodoBinding()
sucht nach dem_
. Sie können das entfernen,if (key.startsWith("_"))
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); } } }
quelle
Wie wäre es mit einer gemeinsamen Funktion, um die Bindungsarbeit wie folgt auszuführen:
// 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){ //... } }) } }
quelle