Funktionen an Komponenten weitergeben
Wie gebe ich einen Event-Handler (wie onClick) an eine Komponente weiter?
Übergebe Event-Handler und andere Funktionen als Props an Unterkomponenten:
<button onClick={this.handleClick}>
Wenn du Zugang zur höher gelegenen Komponente im Handler benötigst, musst du auch die Funktion an die Komponenteninstanz binden (siehe unten).
Wie binde ich eine Funktion an eine Komponenteninstanz?
Es gibt mehrere Möglichkeiten um sicherzustellen, dass Funktionen Zugang zu Komponenteneigenschaften wie this.props
und this.state
haben, abhängig davon welche Syntax oder Build-Schritte benutzt werden.
Binden im Konstruktor (ES2015)
class Foo extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Click happened');
}
render() {
return <button onClick={this.handleClick}>Click Me</button>;
}
}
Klasseneigenschaften (ES2022)
class Foo extends Component {
handleClick = () => {
console.log('Click happened');
};
render() {
return <button onClick={this.handleClick}>Click Me</button>;
}
}
Binden beim Rendern
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
}
}
Hinweis:
Wenn
Function.prototype.bind
beim Rendern benutzt wird, wird jedes mal eine neue Funktion erzeugt wenn die Komponente erneut gerendert wird. Dies kann Leistungseinbußen zur Folge haben (siehe unten).
Pfeilfunktion beim Rendern
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <button onClick={() => this.handleClick()}>Click Me</button>;
}
}
Hinweis:
Wenn Pfeilfunktionen beim Rendern benutzt werden, wird jedes Mal eine neue Funktion erzeugt wenn die Komponente gerendert wird. Dies kann Optimierungen mit strengem Identitätsvergleich zerstören.
Ist es OK Pfeilfunktionen in Rendermethoden zu benutzen?
Allgemein gesagt ja, es ist OK, und es ist oft der einfachste Weg um Parameter an Callback-Funktionen weiterzugeben.
Im Fall von Leistungsproblemen sollte man jedoch optimieren!
Warum ist Binden überhaupt notwendig?
In JavaScript, sind diese beiden Code-Schnipsel nicht equivalent:
obj.method();
var method = obj.method;
method();
Methoden zu Binden trägt dazu bei, dass sich der zweite Code-Schnipsel auf die selbe Weise wie der erste verhält.
Mit React musst du normalerweise nur die Methoden binden, die du an andere Komponenten weitergeben möchtest. Zum Beispiel gibt <button onClick={this.handleClick}>
this.handleClick
weiter, und deshalb sollte es gebunden werden. Es ist jedoch nicht notwendig die render
oder die Lifecycle-Methode zu binden: Wir geben diese Methoden nicht an Komponenten weiter.
Dieser Beitrag von Yehuda Katz erklärt was Binden ist und wie Funktionen in JavaScript im Detail funktionieren.
Warum wird meine Funktion jedes Mal aufgerufen, wenn die Komponente rendert?
Du musst aufpassen, dass du nicht die Funktion aufrufst, wenn du sie and die Komponente übergibst:
render() {
// Wrong: handleClick is called instead of passed as a reference!
return <button onClick={this.handleClick()}>Click Me</button>
}
Stattdessen übergebe die Funktion selbst (ohne Klammern)
render() {
// Correct: handleClick is passed as a reference!
return <button onClick={this.handleClick}>Click Me</button>
}
Wie übergebe ich einen Parameter an einen Event-Handler oder Callback?
Du kannst eine Pfeilfunktion dazu benutzten, um einen Event-Handler zu umschließen und Parameter weiterzugeben:
<button onClick={() => this.handleClick(id)} />
Dies ist identisch zum Aufruf von .bind
:
<button onClick={this.handleClick.bind(this, id)} />
Beispiel: Weitergabe von Parametern mit Hilfe von Pfeilfunktionen
const A = 65 // ASCII character code
class Alphabet extends React.Component {
constructor(props) {
super(props);
this.state = {
justClicked: null,
letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
};
}
handleClick(letter) {
this.setState({ justClicked: letter });
}
render() {
return (
<div>
Just clicked: {this.state.justClicked}
<ul>
{this.state.letters.map(letter =>
<li key={letter} onClick={() => this.handleClick(letter)}>
{letter}
</li>
)}
</ul>
</div>
)
}
}
Beispiel: Weitergabe von Parametern mit Hilfe von data-Attributen
Alternativ können DOM-APIs dazu benutzt werden, Daten für Event-Handler zu speichern. Dies solltest du in betracht ziehen, wenn du eine große Anzahl von Elementen optimieren möchte oder einen Renderbaum benutzt der sich auf die Gleichheitsprüfung von React.PureComponent verlässt.
const A = 65 // ASCII character code
class Alphabet extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
justClicked: null,
letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
};
}
handleClick(e) {
this.setState({
justClicked: e.target.dataset.letter
});
}
render() {
return (
<div>
Just clicked: {this.state.justClicked}
<ul>
{this.state.letters.map(letter =>
<li key={letter} data-letter={letter} onClick={this.handleClick}>
{letter}
</li>
)}
</ul>
</div>
)
}
}
Wie kann ich verhindern, dass eine Funtion entweder zu schnell oder zu oft hintereinander aufgrufen wird.
Wenn du einen Event-Handler wie onClick
oder onScroll
benutzt und verhindern möchtest, dass der Callback zu schnell ausgelöst wird, dann kannst du die Häufigkeit mit der der Callback ausgeführt wird einschränken.
- throttling: Eingabewert wechselt basierend auf einem Zeitinterval (z. Bsp.
_.throttle
) - debouncing: Veröffentlichung von Veränderungen nach einer gewissen Zeit der Inaktivität (z.Bsp.
_.debounce
) requestAnimationFrame
throttling: Eingabewert wechselt basierend aufrequestAnimationFrame
(z. Bsp.raf-schd
)
Siehe diese Visualisierung für einen Vergleich von throttle
und debounce
Funktionen.
Hinweis:
_.debounce
,_.throttle
undraf-schd
bieten einecancel
-Methode um verzögerte Callbacks zu löschen. Du solltest diese Methode entweder voncomponentWillUnmount
aufrufen oder prüfen und sicherstellen, dass die Komponente innerhalb der verzögerten Funktion gemountet ist.
Throttle
Throttling verhindert, dass eine Funktion mehr als einmal innerhalb eines definierten Zeitfensters aufgerufen wird. Das unten angegebene Beispiel throttlet einen “click”-Handler um zu verhindern, dass er mehr als einmal pro Sekunde aufgerufen wird.
import throttle from 'lodash.throttle';
class LoadMoreButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.handleClickThrottled = throttle(this.handleClick, 1000);
}
componentWillUnmount() {
this.handleClickThrottled.cancel();
}
render() {
return <button onClick={this.handleClickThrottled}>Load More</button>;
}
handleClick() {
this.props.loadMore();
}
}
Debounce
Debouncing stellt sicher, dass eine Funktion nur ausgeführt wird, wenn eine bestimmte Zeit seit dem letzten Aufruf verstrichen ist. Das kann hilfreich sein, wenn du aufwendige Berechnungen ausführen musst um auf ein häufig auftretendes Ereignis zu reagieren (z.Bsp. Scrollen oder Tastatur-Eingaben). Das Beispiel unten debouncet eine Texteingabe mit einer Verzögerung von 250ms.
import debounce from 'lodash.debounce';
class Searchbox extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.emitChangeDebounced = debounce(this.emitChange, 250);
}
componentWillUnmount() {
this.emitChangeDebounced.cancel();
}
render() {
return (
<input
type="text"
onChange={this.handleChange}
placeholder="Search..."
defaultValue={this.props.value}
/>
);
}
handleChange(e) {
this.emitChangeDebounced(e.target.value);
}
emitChange(value) {
this.props.onChange(value);
}
}
requestAnimationFrame
throttling
requestAnimationFrame
ist ein Weg um eine Funktion in eine Warteschlange zu stellen um sie dann vom Browser zum optimalen Zeitpunkt für die beste Renderleistung ausführen zu lassen. Eine Funktion, die mit requestAnimationFrame
in die Warteschlange gestellt wird, wifr mit dem nächsten Bildwechsel ausgelöst. Der Browser bemüht sich sehr um 60 Bildwechsel pro Sekunde auszuführen (60 fps). Wenn der Browser dies nicht schafft, dann wir die Anzahl der Bildwechsel pro Sekunde auf natürliche Weise begrenzt. Zum Beispiel ist es möglich, dass ein Gerät nur 30 fps leisten kann, dann gibt es nur 30 Bildwechsel pro Sekunde. Der Einsatz von requestAnimationFrame
zum Throttling ist ein hilfreicher Weg um zu verhindern, dass mehr als 60 Updates pro Sekunde ausgeführt werden. Wenn mehr als 100 Updates pro Sekunde ausgeführt werden erzeugt dies zusätzliche Arbeit für den Browser die der Benutzer sowieso nicht sehen kann.
Hinweis:
Wenn du dieses Verfahren anwendest, wird nur der zuletzt aktivierte Wert beim Bildwechsel festgehalten. Du kannst ein Beispiel, wie diese Optimierung funktioniert auf
MDN
sehen
import rafSchedule from 'raf-schd';
class ScrollListener extends React.Component {
constructor(props) {
super(props);
this.handleScroll = this.handleScroll.bind(this);
// Create a new function to schedule updates.
this.scheduleUpdate = rafSchedule(
point => this.props.onScroll(point)
);
}
handleScroll(e) {
// When we receive a scroll event, schedule an update.
// If we receive many updates within a frame, we'll only publish the latest value.
this.scheduleUpdate({ x: e.clientX, y: e.clientY });
}
componentWillUnmount() {
// Cancel any pending updates since we're unmounting.
this.scheduleUpdate.cancel();
}
render() {
return (
<div
style={{ overflow: 'scroll' }}
onScroll={this.handleScroll}
>
<img src="/my-huge-image.jpg" />
</div>
);
}
}
Testen der Durchsatzratenbegrenzung (engl. rate limiting)
Um zu testen ob der durchsatzratenbegrenzte Code funktioniert ist es hilfreich, die Möglichkeit zu haben, die Zeit vorzuspulen. Wenn du jest
benutzt kannst du mock timers
verwenden um die Zeit vorzuspulen. Wenn du requestAnimationFrame
throttling benutzt, ist raf-stub
ein hilfreiches Werkzeug um den Fortschritt der einzelnen Animation-Frames zu kontrollieren.