Ich versuche, meinen Zustand zu organisieren, indem ich verschachtelte Eigenschaften wie diese verwende:
this.state = {
someProperty: {
flag:true
}
}
Aber Aktualisierung Zustand wie folgt,
this.setState({ someProperty.flag: false });
funktioniert nicht Wie kann das richtig gemacht werden?
Um setState
für ein verschachteltes Objekt zu erstellen, können Sie den folgenden Ansatz verwenden, da setState meines Erachtens keine verschachtelten Aktualisierungen verarbeitet.
_var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})
_
Die Idee ist, ein Dummy-Objekt zu erstellen, das Operationen ausführt und dann den Status der Komponente durch das aktualisierte Objekt ersetzt
Der Spread-Operator erstellt jetzt nur eine verschachtelte Kopie des Objekts. Wenn Ihr Bundesstaat stark verschachtelt ist wie:
_this.state = {
someProperty: {
someOtherProperty: {
anotherProperty: {
flag: true
}
..
}
...
}
...
}
_
Sie können den Status mithilfe des Spread-Operators auf jeder Ebene wie folgt festlegen
_this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
_
Die obige Syntax wird jedoch mit zunehmender Verschachtelung des Status immer hässlicher. Daher empfehle ich Ihnen, das Paket immutability-helper
zu verwenden, um den Status zu aktualisieren.
In dieser Antwort erfahren Sie, wie Sie den Status mit immutability helper
aktualisieren.
Um es in einer Zeile zu schreiben
this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
Manchmal sind direkte Antworten nicht die besten:)
Kurzfassung:
dieser Code
this.state = {
someProperty: {
flag: true
}
}
sollte so etwas wie vereinfacht werden
this.state = {
somePropertyFlag: true
}
Lange Version:
Derzeit Sie sollten nicht mit verschachtelten Status in React arbeiten wollen. Da React nicht auf die Arbeit mit verschachtelten Zuständen ausgerichtet ist und alle hier vorgeschlagenen Lösungen als Hacks angezeigt werden. Sie nutzen das Framework nicht, sondern kämpfen damit. Sie schlagen vor, nicht so klaren Code zu schreiben, um bestimmte Eigenschaften zweifelhaft zu gruppieren. Sie sind also als Antwort auf die Herausforderung sehr interessant, aber praktisch nutzlos.
Stellen wir uns folgenden Zustand vor:
{
parent: {
child1: 'value 1',
child2: 'value 2',
...
child100: 'value 100'
}
}
Was passiert, wenn Sie nur einen Wert von child1
ändern? React rendert die Ansicht nicht erneut, da ein flacher Vergleich verwendet wird und sich die Eigenschaft parent
nicht geändert hat. Übrigens wird das direkte Mutieren des Zustandsobjekts im Allgemeinen als schlechte Praxis angesehen.
Sie müssen also das gesamte Objekt parent
neu erstellen. In diesem Fall werden wir jedoch auf ein anderes Problem stoßen. React wird denken, dass alle Kinder ihre Werte geändert haben, und wird sie alle neu rendern. Natürlich ist es nicht gut für die Leistung.
Es ist immer noch möglich, dieses Problem durch Schreiben einer komplizierten Logik in shouldComponentUpdate()
zu lösen, aber ich würde es vorziehen, hier anzuhalten und eine einfache Lösung aus der Kurzversion zu verwenden.
Verschachtelter Status in React ist falsch
Lesen Sie diese ausgezeichnete Antwort .
Begründung hinter dieser Antwort:
Reacts setState ist nur ein integrierter Komfort, aber Sie werden schnell feststellen, dass er seine Grenzen hat. Durch die Verwendung benutzerdefinierter Eigenschaften und den intelligenten Einsatz von forceUpdate erhalten Sie viel mehr. z.B:
class MyClass extends React.Component { myState = someObject inputValue = 42 ...
MobX, zum Beispiel, gräbt den Status vollständig aus und verwendet benutzerdefinierte beobachtbare Eigenschaften.
Verwenden Sie Observables anstelle von state in React Komponenten.
Es gibt eine andere kürzere Möglichkeit, verschachtelte Eigenschaften zu aktualisieren.
this.setState(state => {
state.nested.flag = false
state.another.deep.prop = true
return state
})
this.setState(state => (state.nested.flag = false, state))
Dies ist praktisch dasselbe wie
this.state.nested.flag = false
this.forceUpdate()
Für den subtilen Unterschied in diesem Zusammenhang zwischen forceUpdate
und setState
siehe das verknüpfte Beispiel.
Dies missbraucht natürlich einige Kernprinzipien, da state
nur lesbar sein sollte, aber da Sie den alten Zustand sofort verwerfen und durch einen neuen Zustand ersetzen, ist dies völlig in Ordnung.
Auch wenn die Komponente, die den Status wird enthält, ordnungsgemäß aktualisiert und neu gerendert wird ( mit Ausnahme dieses Punktes ), werden die Requisiten fehlschlagen an Kinder zu verbreiten (siehe Spymasters Kommentar unten). Wenden Sie diese Technik nur an, wenn Sie wissen, was Sie tun.
Beispielsweise können Sie eine geänderte flache Requisite übergeben, die aktualisiert und problemlos übergeben wird.
render(
//some complex render with your nested state
<ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)
Jetzt, obwohl sich die Referenz für complexNestedProp nicht geändert hat ( shouldComponentUpdate )
this.props.complexNestedProp === nextProps.complexNestedProp
die Komponente wird wird immer dann neu gerendert, wenn die übergeordnete Komponente aktualisiert wird. Dies ist der Fall, wenn this.setState
oder this.forceUpdate
in der übergeordneten Komponente aufgerufen wurde.
Das Verwenden von verschachtelten Status und das direkte Mutieren des Status ist gefährlich, da unterschiedliche Objekte (absichtlich oder nicht) unterschiedliche (ältere) Verweise auf den Status und wissen möglicherweise nicht unbedingt, wann eine Aktualisierung durchgeführt werden muss (z. B. bei Verwendung von PureComponent
oder wenn shouldComponentUpdate
implementiert ist, um false
zurückzugeben) ODER sollen alte Daten wie im folgenden Beispiel anzeigen.
Stellen Sie sich eine Zeitachse vor, die historische Daten rendern soll. Wenn Sie die Daten unter der Hand ändern, wird dies zu unerwartetem Verhalten führen, da auch frühere Elemente geändert werden.
Wie auch immer, Sie können hier sehen, dass Nested PureChildClass
aufgrund fehlender Propagierung der Requisiten nicht erneut gerendert wurde.
Wenn Sie ES2015 verwenden, haben Sie Zugriff auf Object.assign. Sie können es folgendermaßen verwenden, um ein verschachteltes Objekt zu aktualisieren.
this.setState({
someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});
Sie führen die aktualisierten Eigenschaften mit den vorhandenen zusammen und aktualisieren den Status mithilfe des zurückgegebenen Objekts.
Bearbeiten: Der Zuweisungsfunktion wurde ein leeres Objekt als Ziel hinzugefügt, um sicherzustellen, dass der Status nicht direkt mutiert wird, wie carkod hervorhob.
Es gibt viele Bibliotheken, die dabei helfen. Zum Beispiel mit nveränderlichkeitshilfe :
import update from 'immutability-helper';
const newState = update(this.state, {
someProperty: {flag: {$set: false}},
};
this.setState(newState);
Mit lodash/fp setze:
import {set} from 'lodash/fp';
const newState = set(["someProperty", "flag"], false, this.state);
Mit lodash/fp merge:
import {merge} from 'lodash/fp';
const newState = merge(this.state, {
someProperty: {flag: false},
});
Mit ES6:
this.setState({...this.state, property: {nestedProperty: "new value"}})
Welches ist gleichbedeutend mit:
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);
Wir verwenden Immer https://github.com/mweststrate/immer , um diese Art von Problemen zu behandeln.
Ersetzte gerade diesen Code in einer unserer Komponenten
this.setState(prevState => ({
...prevState,
preferences: {
...prevState.preferences,
[key]: newValue
}
}));
Mit diesem
import produce from 'immer';
this.setState(produce(draft => {
draft.preferences[key] = newValue;
}));
Mit immer behandeln Sie Ihren Zustand als "normales Objekt". Die Magie geschieht hinter den Kulissen mit Proxy-Objekten.
Hier ist eine Variation der ersten Antwort in diesem Thread, für die keine zusätzlichen Pakete, Bibliotheken oder Sonderfunktionen erforderlich sind.
state = {
someProperty: {
flag: 'string'
}
}
handleChange = (value) => {
const newState = {...this.state.someProperty, flag: value}
this.setState({ someProperty: newState })
}
Um den Status eines bestimmten verschachtelten Felds festzulegen, haben Sie das gesamte Objekt festgelegt. Dazu habe ich die Variable newState
erstellt und den Inhalt des aktuellen Status darin verteilt first mithilfe des ES2015 spread operator . Dann habe ich den Wert von this.state.flag
durch den neuen Wert ersetzt (da ich flag: value
nach gesetzt habe, habe ich den aktuellen Status in das Objekt, das Feld flag
in, verteilt Der aktuelle Status wird überschrieben. Dann setze ich einfach den Status von someProperty
auf mein newState
Objekt.
Ich habe diese Lösung benutzt.
Wenn Sie einen verschachtelten Status wie diesen haben:
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
sie können die Funktion handleChange deklarieren, die den aktuellen Status kopiert und ihn mit geänderten Werten neu zuweist
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let statusCopy = Object.assign({}, this.state);
statusCopy.formInputs[inputName].value = inputValue;
this.setState(statusCopy);
}
hier das html mit dem eventlistener
<input type="text" onChange={this.handleChange} " name="friendName" />
Erstellen Sie eine Kopie des Staates:let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))
änderungen an diesem Objekt vornehmen:someProperty.flag = "false"
aktualisieren Sie jetzt den Statusthis.setState({someProperty})
Obwohl das Verschachteln nicht wirklich so ist, wie Sie einen Komponentenzustand behandeln sollten, kann dies für das einstufige Verschachteln hilfreich sein.
Für einen Staat wie diesen
state = {
contact: {
phone: '888-888-8888',
email: '[email protected]'
}
address: {
street:''
},
occupation: {
}
}
Eine wiederverwendbare Methode würde so aussehen.
handleChange = (obj) => e => {
let x = this.state[obj];
x[e.target.name] = e.target.value;
this.setState({ [obj]: x });
};
dann geben Sie einfach den obj-Namen für jede Verschachtelung ein, die Sie ansprechen möchten ...
<TextField
name="street"
onChange={handleChange('address')}
/>
Um die Dinge allgemeiner zu gestalten, habe ich an den Antworten von @ ShubhamKhatri und @ Qwerty gearbeitet.
Statusobjekt
this.state = {
name: '',
grandParent: {
parent1: {
child: ''
},
parent2: {
child: ''
}
}
};
Eingabesteuerung
<input
value={this.state.name}
onChange={this.updateState}
type="text"
name="name"
/>
<input
value={this.state.grandParent.parent1.child}
onChange={this.updateState}
type="text"
name="grandParent.parent1.child"
/>
<input
value={this.state.grandParent.parent2.child}
onChange={this.updateState}
type="text"
name="grandParent.parent2.child"
/>
pdateState-Methode
setState als @ ShubhamKhatri Antwort
updateState(event) {
const path = event.target.name.split('.');
const depth = path.length;
const oldstate = this.state;
const newstate = { ...oldstate };
let newStateLevel = newstate;
let oldStateLevel = oldstate;
for (let i = 0; i < depth; i += 1) {
if (i === depth - 1) {
newStateLevel[path[i]] = event.target.value;
} else {
newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
oldStateLevel = oldStateLevel[path[i]];
newStateLevel = newStateLevel[path[i]];
}
}
this.setState(newstate);
}
setState als @ Qwertys Antwort
updateState(event) {
const path = event.target.name.split('.');
const depth = path.length;
const state = { ...this.state };
let ref = state;
for (let i = 0; i < depth; i += 1) {
if (i === depth - 1) {
ref[path[i]] = event.target.value;
} else {
ref = ref[path[i]];
}
}
this.setState(state);
}
Hinweis: Diese oben genannten Methoden funktionieren nicht für Arrays
Ich nehme die Bedenken sehr ernst, die bereits beim Erstellen einer vollständigen Kopie Ihres Komponentenzustands geäußert wurden . Vor diesem Hintergrund würde ich dringend empfehlen Immer.
import produce from 'immer';
<Input
value={this.state.form.username}
onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
Dies sollte für React.PureComponent
(d. H. Vergleiche flacher Zustände nach React) funktionieren, da Immer
ein Proxy-Objekt geschickt verwendet, um einen beliebig tiefen Zustandsbaum effizient zu kopieren. Immer ist auch typsicherer als Bibliotheken wie Immutability Helper und eignet sich sowohl für Javascript- als auch für TypeScript-Benutzer.
TypeScript Dienstprogrammfunktion
function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s:
Draft<Readonly<S>>) => any) {
comp.setState(produce(comp.state, s => { fn(s); }))
}
onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}
Zwei weitere Optionen, die noch nicht erwähnt wurden:
stateUpdate = () => {
let obj = this.state;
if(this.props.v12_data.values.email) {
obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
}
this.setState(obj)
}
Ich weiß, dass es eine alte Frage ist, wollte aber trotzdem mitteilen, wie ich dies erreicht habe. Angenommen, der Status im Konstruktor sieht folgendermaßen aus:
constructor(props) {
super(props);
this.state = {
loading: false,
user: {
email: ""
},
organization: {
name: ""
}
};
this.handleChange = this.handleChange.bind(this);
}
Meine handleChange
-Funktion sieht folgendermaßen aus:
handleChange(e) {
const names = e.target.name.split(".");
const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
this.setState((state) => {
state[names[0]][names[1]] = value;
return {[names[0]]: state[names[0]]};
});
}
Und stellen Sie sicher, dass Sie die Eingaben entsprechend benennen:
<input
type="text"
name="user.email"
onChange={this.handleChange}
value={this.state.user.firstName}
placeholder="Email Address"
/>
<input
type="text"
name="organization.name"
onChange={this.handleChange}
value={this.state.organization.name}
placeholder="Organization Name"
/>
Ich fand, dass dies für mich funktioniert, da ich in meinem Fall ein Projektformular habe, in dem Sie beispielsweise eine ID und einen Namen haben und den Status für ein verschachteltes Projekt lieber beibehalten möchten.
return (
<div>
<h2>Project Details</h2>
<form>
<Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} />
<Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} />
</form>
</div>
)
Gib mir Bescheid!
Ich habe Updates mit einem verkleinern verschachtelt suchen:
Beispiel:
Die verschachtelten Variablen in state:
state = {
coords: {
x: 0,
y: 0,
z: 0
}
}
Die Funktion:
handleChange = nestedAttr => event => {
const { target: { value } } = event;
const attrs = nestedAttr.split('.');
let stateVar = this.state[attrs[0]];
if(attrs.length>1)
attrs.reduce((a,b,index,arr)=>{
if(index==arr.length-1)
a[b] = value;
else if(a[b]!=null)
return a[b]
else
return a;
},stateVar);
else
stateVar = value;
this.setState({[attrs[0]]: stateVar})
}
Verwenden Sie:
<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>
So etwas könnte ausreichen,
const isObject = (thing) => {
if(thing &&
typeof thing === 'object' &&
typeof thing !== null
&& !(Array.isArray(thing))
){
return true;
}
return false;
}
/*
Call with an array containing the path to the property you want to access
And the current component/redux state.
For example if we want to update `hello` within the following obj
const obj = {
somePrimitive:false,
someNestedObj:{
hello:1
}
}
we would do :
//clone the object
const cloned = clone(['someNestedObj','hello'],obj)
//Set the new value
cloned.someNestedObj.hello = 5;
*/
const clone = (arr, state) => {
let clonedObj = {...state}
const originalObj = clonedObj;
arr.forEach(property => {
if(!(property in clonedObj)){
throw new Error('State missing property')
}
if(isObject(clonedObj[property])){
clonedObj[property] = {...originalObj[property]};
clonedObj = clonedObj[property];
}
})
return originalObj;
}
const nestedObj = {
someProperty:true,
someNestedObj:{
someOtherProperty:true
}
}
const clonedObj = clone(['someProperty'], nestedObj);
console.log(clonedObj === nestedObj) //returns false
console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true
console.log()
const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
console.log(clonedObj2 === nestedObj) // returns false
console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
//returns true (doesn't attempt to clone because its primitive type)
console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty)