Я пытаюсь организовать свое состояние, используя вложенное свойство следующим образом:
this.state = {
someProperty: {
flag:true
}
}
Но обновляя состояние, подобное этому,
this.setState({ someProperty.flag: false });
не работает. Как это можно сделать правильно?
Чтобы setState
для вложенного объекта вы можете следовать подходу ниже, я думаю, что setState не обрабатывает вложенные обновления.
var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})
Идея состоит в том, чтобы создать фиктивный объект, выполняющий на нем операции, а затем заменить состояние компонента обновленным объектом
Теперь оператор распространения создает только одну вложенную копию объекта. Если ваше состояние сильно вложено, например:
this.state = {
someProperty: {
someOtherProperty: {
anotherProperty: {
flag: true
}
..
}
...
}
...
}
Вы можете установитьState с помощью оператора спредов на каждом уровне, например
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
Однако вышеприведенный синтаксис становится все уродливым, поскольку состояние становится все более и более вложенным, и поэтому я рекомендую вам использовать пакет immutability-helper
для обновления состояния.
См. Этот ответ о том, как обновить состояние с помощью immutability helper
.
someProperty
и мы someProperty
это
Чтобы записать его в одну строку
this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
this.state
сразу после setState
и ожидайте, что он будет иметь новое значение. По сравнению с вызовом this.state
в setState
. Или есть проблема с последним, которая мне не ясна?
Иногда прямые ответы не самые лучшие :)
Укороченная версия:
этот код
this.state = {
someProperty: {
flag: true
}
}
должен быть упрощен как нечто вроде
this.state = {
somePropertyFlag: true
}
Длинная версия:
В настоящее время вы не должны работать с вложенным состоянием в React. Поскольку React не ориентирован на работу с вложенными состояниями, и все предлагаемые здесь решения выглядят как хаки. Они не используют фреймворк, но сражаются с ним. Они предлагают написать не столь четкий код для сомнительной цели группировки некоторых свойств. Поэтому они очень интересны как ответ на вызов, но практически бесполезны.
Представьте себе следующее состояние:
{
parent: {
child1: 'value 1',
child2: 'value 2',
...
child100: 'value 100'
}
}
Что произойдет, если вы измените только значение child1
? React не будет повторно отображать представление, потому что он использует неглубокое сравнение, и он обнаружит, что parent
свойство не изменилось. BTW, мутирующий объект состояния напрямую, считается плохой практикой в целом.
Поэтому вам нужно воссоздать весь parent
объект. Но в этом случае мы встретим еще одну проблему. Реакция будет думать, что все дети изменили свои ценности и будут повторно отображать их все. Конечно, это плохо для производительности.
По-прежнему можно решить эту проблему, написав сложную логику в shouldComponentUpdate()
но я предпочел бы остановиться здесь и использовать простое решение из короткой версии.
Вложенное состояние в React - неправильный дизайн
Прочитайте этот отличный ответ.
Причины этого ответа:
React setState - это просто встроенное удобство, но вы скоро поймете, что оно имеет свои пределы. Использование пользовательских свойств и интеллектуальное использование forceUpdate дает вам гораздо больше. например:
class MyClass extends React.Component { myState = someObject inputValue = 42 ...
Например, MobX полностью скрывает состояние канав и использует настраиваемые наблюдаемые свойства.
Используйте Observables вместо состояния в компонентах React.
Существует еще один более короткий способ обновления любого вложенного свойства.
this.setState(state => {
state.nested.flag = false
state.another.deep.prop = true
return state
})
this.setState(state => (state.nested.flag = false, state))
Конечно, это злоупотребление некоторыми основными принципами, так как state
должно быть только для чтения, но, поскольку вы немедленно отбрасываете старое состояние и заменяете его новым, это вполне нормально.
Даже если компонент, содержащий состояние, будет обновляться и перерисовываться должным образом (кроме этой ошибки), реквизиты не смогут распространяться на детей (см. Комментарий Spymaster ниже). Используйте эту технику, только если вы знаете, что делаете.
Например, вы можете передать измененную плоскую подпорку, которая обновляется и легко передается.
render(
//some complex render with your nested state
<ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)
Теперь, хотя ссылка на complexNestedProp не изменилась (shouldComponentUpdate)
this.props.complexNestedProp === nextProps.complexNestedProp
компонент будет перерисовываться всякий раз, когда родительский компонент обновляется, что имеет место после вызова this.setState
или this.forceUpdate
в родительском.
state
канав и использует настраиваемые наблюдаемые свойства. React setState
- это просто встроенное удобство, но вы скоро поймете, что у него есть свои пределы. Использование пользовательских свойств и интеллектуальное использование forceUpdate
дает вам гораздо больше. Используйте Observables вместо состояния в компонентах React.
Если вы используете ES2015, у вас есть доступ к Object.assign. Вы можете использовать его следующим образом, чтобы обновить вложенный объект.
this.setState({
someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});
Вы объединяете обновленные свойства с существующим и используете возвращенный объект для обновления состояния.
Редактировать: добавлен пустой объект в качестве цели в функцию назначения, чтобы убедиться, что состояние не мутировано напрямую, как указывал Каркод.
Для этого есть много библиотек. Например, используя универсальный помощник:
import update from 'immutability-helper';
const newState = update(this.state, {
someProperty: {flag: {$set: false}},
};
this.setState(newState);
Или используя lodash/fp:
import {merge} from 'lodash/fp';
const newState = merge(this.state, {
someProperty: {flag: false},
});
lodash
, вы, вероятно, захотите попробовать _.cloneDeep
и установить состояние в качестве клонированной копии.
lodash/fp
, поэтому нам не нужно беспокоиться о мутациях.
Вы также можете пойти таким образом (что мне кажется более читаемым):
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = 'newValue';
this.setState(newState);
Здесь вариация первого ответа, данного в этом потоке, который не требует дополнительных пакетов, библиотек или специальных функций.
state = {
someProperty: {
flag: 'string'
}
}
handleChange = (value) => {
const newState = {...this.state.someProperty, flag: value}
this.setState({ someProperty: newState })
}
Чтобы установить состояние определенного вложенного поля, вы установили весь объект. Я сделал это, создав переменную newState
и распространив содержимое текущего состояния в нее сначала с помощью оператора распространения ES2015. Затем я заменил значение this.state.flag
новым значением (поскольку я устанавливаю flag: value
после того, как я распространил текущее состояние на объект, поле flag
в текущем состоянии переопределено). Затем я просто someProperty
состояние someProperty
для моего объекта newState
.
Мы используем Immer https://github.com/mweststrate/immer для решения этих проблем.
Просто заменил этот код на одном из наших компонентов
this.setState(prevState => ({
...prevState,
preferences: {
...prevState.preferences,
[key]: newValue
}
}));
С этим
import produce from 'immer';
this.setState(produce(draft => {
draft.preferences[key] = newValue;
}));
С помощью immer вы обрабатываете свое состояние как "обычный объект". Магия происходит за сценой с прокси-объектами.
Два других варианта, которые еще не упомянуты:
Я использовал это решение.
Если у вас есть вложенное состояние:
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
вы можете объявить функцию handleChange, которая копирует текущее состояние и повторно назначает его с измененными значениями
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);
}
здесь html с прослушивателем событий
<input type="text" onChange={this.handleChange} " name="friendName" />
Я очень серьезно отношусь к проблемам, уже высказанным в связи с созданием полной копии состояния вашего компонента. С учетом сказанного, я настоятельно рекомендую Immer.
import produce from 'immer';
<Input
value={this.state.form.username}
onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
Это должно работать для React.PureComponent
(т. React.PureComponent
сравнение состояний с помощью React), поскольку Immer
ловко использует прокси-объект для эффективного копирования произвольно глубокого дерева состояний. Immer также более безопасен по сравнению с такими библиотеками, как Immutability Helper, и идеально подходит для пользователей Javascript и Typescript.
Полезность Typescript
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)}
Создайте копию состояния: let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))
внести изменения в этот объект: someProperty.flag = "false"
сейчас обновите состояние this.setState({someProperty})
Что-то вроде этого может быть достаточно,
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)
Чтобы сделать вещи родовыми, я работал над ответами @ShubhamKhatri и @Qwerty.
объект состояния
this.state = {
name: '',
grandParent: {
parent1: {
child: ''
},
parent2: {
child: ''
}
}
};
элементы управления вводом
<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"
/>
Метод updateState
setState как @ShubhamKhatri ответить
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 как ответ @Qwerty
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);
}
Примечание. Эти методы не будут работать для массивов
Я нашел, что это работает для меня, имея форму проекта в моем случае, где, например, у вас есть идентификатор и имя, и я бы предпочел сохранить состояние для вложенного проекта.
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>
)
Дай мне знать!