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 currentNameIndexauf 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 componentDidUpdatenach 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 getDerivedStateFromPropsMethode ist staticund 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
}
})
}
}
nextan 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 übernimmt

currentIndex. Wennnamesim übergeordneten Element aktualisiert werden, einfach zurücksetzencurrentIndex