Ich habe eine sehr einfache Komponente mit einem Textfeld und einer Schaltfläche:
Es nimmt eine Liste als Eingabe und ermöglicht dem Benutzer, durch die Liste zu blättern.
Die Komponente hat den folgenden Code:
import * as React from "react";
import {Button} from "@material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
}
Diese Komponente funktioniert hervorragend, außer ich habe den Fall nicht behandelt, wenn sich der Status ändert. Wenn sich der Zustand ändert, möchte ich den currentNameIndex
auf Null zurücksetzen .
Was ist der beste Weg, dies zu tun?
Optionen, die ich berücksichtigt habe:
Verwenden von componentDidUpdate
Diese Lösung ist rückständig, da sie componentDidUpdate
nach dem Rendern ausgeführt wird. Daher muss ich in der Rendermethode eine Klausel hinzufügen, um "nichts zu tun", während sich die Komponente in einem ungültigen Zustand befindet. Wenn ich nicht aufpasse, kann ich eine Nullzeigerausnahme verursachen .
Ich habe unten eine Implementierung davon aufgenommen.
Verwenden von getDerivedStateFromProps
Die getDerivedStateFromProps
Methode ist static
und die Signatur gibt Ihnen nur Zugriff auf den aktuellen Status und die nächsten Requisiten. Dies ist ein Problem, da Sie nicht feststellen können, ob sich die Requisiten geändert haben. Infolgedessen müssen Sie die Requisiten in den Status kopieren, damit Sie überprüfen können, ob sie identisch sind.
Die Komponente "vollständig gesteuert" machen
Ich will das nicht machen. Diese Komponente sollte privat den aktuell ausgewählten Index besitzen.
Die Komponente "mit einem Schlüssel völlig unkontrolliert" machen
Ich denke über diesen Ansatz nach, mag aber nicht, wie der Elternteil die Implementierungsdetails des Kindes verstehen muss.
Sonstiges
Ich habe viel Zeit damit verbracht, zu lesen, dass Sie wahrscheinlich keinen abgeleiteten Zustand benötigen , bin aber mit den dort vorgeschlagenen Lösungen weitgehend unzufrieden.
Ich weiß, dass Variationen dieser Frage mehrfach gestellt wurden, aber ich habe nicht das Gefühl, dass eine der Antworten die möglichen Lösungen abwägt. Einige Beispiele für Duplikate:
- So setzen Sie den Status einer Komponente beim Propellerwechsel zurück
- Aktualisieren Sie den Komponentenstatus, wenn sich die Requisiten ändern
Blinddarm
Lösung mit componetDidUpdate
(siehe Beschreibung oben)
import * as React from "react";
import {Button} from "@material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
if(this.state.currentNameIndex >= this.props.names.length){
return "Cannot render the component - after compoonentDidUpdate runs, everything will be fixed"
}
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>): void {
if(prevProps.names !== this.props.names){
this.setState({
currentNameIndex: 0
})
}
}
}
Lösung mit getDerivedStateFromProps
:
import * as React from "react";
import {Button} from "@material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
copyOfProps?: Props
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
static getDerivedStateFromProps(props: Props, state: State): Partial<State> {
if( state.copyOfProps && props.names !== state.copyOfProps.names){
return {
currentNameIndex: 0,
copyOfProps: props
}
}
return {
copyOfProps: props
}
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
}
quelle
currentIndex
. Wennnames
im übergeordneten Element aktualisiert werden, einfach zurücksetzencurrentIndex
next
an die Komponente. Die Schaltfläche zeigt den Namen an und ihr onClick ruft nur die nächste Funktion auf, die sich im übergeordneten Element befindet und das Radfahren übernimmtAntworten:
Können Sie eine Funktionskomponente verwenden? Könnte die Dinge ein bisschen vereinfachen.
useEffect
ähnelt dem Ort, ancomponentDidUpdate
dem ein Array von Abhängigkeiten (Status- und Prop-Variablen) als zweites Argument verwendet wird. Wenn sich diese Variablen ändern, wird die Funktion im ersten Argument ausgeführt. So einfach ist das. Sie können zusätzliche Logikprüfungen innerhalb des Funktionskörpers durchführen, um Variablen festzulegen (zsetCurrentNameIndex
. B. ).Seien Sie nur vorsichtig, wenn Sie eine Abhängigkeit im zweiten Argument haben, die innerhalb der Funktion geändert wird, dann haben Sie unendlich viele Renderings.
Schauen Sie sich die useEffect-Dokumente an , aber Sie werden wahrscheinlich nie wieder eine Klassenkomponente verwenden wollen, nachdem Sie sich an Hooks gewöhnt haben .
quelle
useEffect
. Wenn Sie aktualisieren, um zu erklären, warum dies funktioniert, werde ich dies als akzeptierte Antwort markieren.Wie ich in den Kommentaren sagte, bin ich kein Fan dieser Lösungen.
Komponenten sollten sich nicht darum kümmern, was der Elternteil tut oder was der Strom
state
des Elternteils ist. Sie sollten einfachprops
einige aufnehmen und ausgebenJSX
. Auf diese Weise sind sie wirklich wiederverwendbar, zusammensetzbar und isoliert, was auch das Testen erheblich erleichtert.Wir können die
NamesCarousel
Komponente dazu bringen, die Namen des Karussells zusammen mit der Funktionalität des Karussells und dem aktuell sichtbaren NamenName
zu speichern, und eine Komponente erstellen, die nur eines tut: den Namen anzeigen, der durchkommtprops
Um das Zurücksetzen der
selectedIndex
Elemente zurückzusetzen, fügen Sie einuseEffect
mit Elementen als Abhängigkeit hinzu. Wenn Sie jedoch nur Elemente am Ende des Arrays hinzufügen, können Sie diesen Teil ignorierenNun ist das in Ordnung, aber ist das
NamesCarousel
wiederverwendbar? Nein, dieName
Komponente ist aber dieCarousel
ist mit derName
Komponente gekoppelt .Was können wir also tun, um es wirklich wiederverwendbar zu machen und die Vorteile des isolierten Entwurfs von Komponenten zu sehen ?
Wir können das Render-Requisitenmuster nutzen .
Lassen Sie
Carousel
uns eine generische Komponente erstellen, die eine generische Liste vonitems
und diechildren
Funktion aufruft, die in der ausgewählten übergeben wirditem
Was gibt uns dieses Muster eigentlich?
Es gibt uns die Möglichkeit, die
Carousel
Komponente so zu rendernJetzt sind sie beide isoliert und wirklich wiederverwendbar. Sie können sie
items
als eine Reihe von Bildern, Videos oder sogar Benutzern übergeben.Sie können noch weiter gehen und dem Karussell die Anzahl der Elemente geben, die Sie als Requisiten anzeigen möchten, und die untergeordnete Funktion mit einer Reihe von Elementen aufrufen
quelle
selectedIndex
wenn sich die Requisiten ändern, oder? Wenn das so ist, wie?Sie fragen, was die beste Option ist. Die beste Option besteht darin, sie zu einer kontrollierten Komponente zu machen.
Die Komponente ist in der Hierarchie zu niedrig, um zu wissen, wie mit sich ändernden Eigenschaften umgegangen werden soll. Wenn sich die Liste nur geringfügig ändert (möglicherweise wird ein neuer Name hinzugefügt), möchte die aufrufende Komponente möglicherweise die ursprüngliche Position beibehalten.
In allen Fällen kann ich mir vorstellen, dass es uns besser geht, wenn die übergeordnete Komponente entscheiden kann, wie sich die Komponente verhalten soll, wenn eine neue Liste bereitgestellt wird.
Es ist auch wahrscheinlich, dass eine solche Komponente Teil eines größeren Ganzen ist und die aktuelle Auswahl an das übergeordnete Element übergeben muss - möglicherweise als Teil eines Formulars.
Wenn Sie wirklich darauf aus sind, es nicht zu einer kontrollierten Komponente zu machen, gibt es andere Möglichkeiten:
useEffect
einverstanden sind, ist dies , wie Asaf Aviv vorgeschlagen hat, eine sehr saubere Methode.getDerivedStateFromProps
- und ja, das bedeutet, einen Verweis auf die Namensliste im Status zu behalten und sie zu vergleichen. Es kann ein bisschen besser aussehen, wenn Sie es so schreiben:(Sie sollten wahrscheinlich auch
state.names
die Rendermethode verwenden, wenn Sie diese Route wählen.)Aber eine wirklich kontrollierte Komponente ist der richtige Weg. Sie werden es wahrscheinlich sowieso früher oder später tun, wenn sich die Anforderungen ändern und der Elternteil das ausgewählte Element kennen muss.
quelle
The component is too low in the hierarchy to know how to handle it's properties changing
ist dies die beste Antwort. Die von Ihnen angebotenen Alternativen sind interessant und nützlich. Der Hauptgrund, warum ich diese Requisite nicht verfügbar machen möchte, ist, dass diese Komponente in mehreren Projekten verwendet wird und die API minimal sein soll.If you are ok with hooks, than useEffect as Asaf Aviv suggested is a very clean way to do it.
- Dies löst das Problem nicht - trotz 4 Stimmen setzt diese Frage den Status beim Requisitenwechsel nicht zurück.useEffect(() => { setSelectedIndex(0) }, [items])
Das zweite Argument von UseEffect definiert, wann es ausgeführt werden soll - so effektiv, dass es ausgeführt wird,setSelectedIndex(0)
wenn sich Elemente ändern. Welches ist Ihre Anforderung, soweit ich es verstehe.useEffect
in meiner Lösung setzt den Index zurück, wennnames
Änderungen vorgenommen werden, Tunn'suseEffect
ist ungültig und zeigt Flusenfehler und wirft Fehler aus, wenn sich Namen ändern.names
direkt mutieren, wenn es für Sie nicht zurückgesetzt wird. Dies ist ein weiteres Problem.