Как обновить вложенные свойства состояния в React
Я пытаюсь организовать свое состояние с помощью вложенного свойства следующим образом:
this.state = {
someProperty: {
flag:true
}
}
но обновление состояния, как это,
this.setState({ someProperty.flag: false });
не работает. Как это можно сделать правильно?
14 ответов:
чтобы
setState
для вложенного объекта вы можете следовать приведенному ниже подходу, поскольку я думаю, что setState не обрабатывает вложенные обновления.var someProperty = {...this.state.someProperty} someProperty.flag = true; this.setState({someProperty})
идея состоит в том, чтобы создать фиктивный объект, выполнить над ним операции, а затем заменить состояние компонента обновленным объектом
теперь оператор spread создает только одну вложенную копию объекта уровня. Если ваше состояние сильно вложено, например:
this.state = { someProperty: { someOtherProperty: { anotherProperty: { flag: true } .. } ... } ... }
вы можете установить состояние с помощью оператора spread на каждом уровне как
this.setState(prevState => ({ ...prevState, someProperty: { ...prevState.someProperty, someOtherProperty: { ...prevState.someProperty.someOtherProperty, anotherProperty: { ...prevState.someProperty.someOtherProperty.anotherProperty, flag: false } } } }))
однако приведенный выше синтаксис становится все уродливее, поскольку состояние становится все более и более вложенным, и поэтому я рекомендую вам использовать
immutability-helper
пакет для обновления состояния.см. этот ответ о том, как обновить состояние с помощью
immutability helper
.
написать его в одну строку
this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
Если вы используете ES2015 у вас есть доступ к объекту.назначить. Вы можете использовать его следующим образом для обновления вложенного объекта.
this.setState({ someProperty: Object.assign({}, this.state.someProperty, {flag: false}) });
вы объединяете обновленные свойства с существующими и используете возвращенный объект для обновления состояния.
Edit: добавлен пустой объект в качестве цели в функцию assign, чтобы убедиться, что состояние не мутирует напрямую, как указал carkod.
Иногда прямые ответы не самые лучшие:)
короткая версия:
код
this.state = { someProperty: { flag: true } }
должны быть упрощены как что-то вроде
this.state = { somePropertyFlag: true }
версия:
в настоящее время вы не должны хотеть работать с вложенным состоянием в React. Потому что React не ориентирован на работу с вложенными состояниями и все предлагаемые здесь решения выглядят как хаки. Они не используйте фреймворк, но боритесь с ним. Они предлагают написать не очень понятный код для сомнительной цели группировки некоторых свойств. Поэтому они очень интересны, как ответ на вызов, но практически бесполезно.
представим себе следующее состояние:
{ parent: { child1: 'value 1', child2: 'value 2', ... child100: 'value 100' } }
что произойдет, если вы измените только значение
child1
? React не будет повторно отображать представление, потому что он использует неглубокое сравнение, и он найдет, чтоparent
свойство не изменилось. Кстати мутируя состояние объект непосредственно считается плохой практикой в целом.так что вам нужно воссоздать все
лучше всего - см. пример здесь
есть еще короче способ обновления любого вложенного свойства.
this.setState(state => { state.nested.flag = false state.another.deep.prop = true return state })
в одну строку
this.setState(state => (state.nested.flag = false, state))
конечно, это злоупотребление некоторыми основными принципами, как
state
должно быть только для чтения, но так как вы сразу отбрасываете старое состояние и заменяете его новым состоянием, оно полностью ладно.предупреждение
даже если компонент, содержащий состояние будет обновление и rerender правильно (кроме этого Гоча), то реквизит будет fail размножать детей (см. комментарий Spymaster ниже). Используйте эту технику только в том случае, если вы знаете, что делаете.
например, вы можете передать измененную плоскую опору, которая обновляется и передается легко.
render( //some complex render with your nested state <ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/> )
теперь, хотя ссылка на complexNestedProp не изменилась (shouldComponentUpdate)
this.props.complexNestedProp === nextProps.complexNestedProp
компонент будет rerender всякий раз, когда родительский компонент обновляется, что имеет место после вызова
this.setState
илиthis.forceUpdate
в Родительском.отказ от ответственности
вложенное состояние в React-это неправильный дизайн
читать это отлично ответ.
есть много библиотек, чтобы помочь с этим. Например, с помощью неизменность-помощника:
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}, });
вот вариант первого ответа, данного в этом потоке, который не требует никаких дополнительных пакетов, библиотек или специальных функций.
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
мой
вы также можете пойти этим путем (который кажется мне более читаемым):
const newState = Object.assign({}, this.state); newState.property.nestedProperty = 'newValue'; this.setState(newState);
я использовал это решение.
Если у вас есть вложенный такой:
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" />
два других варианта еще не упомянул:
- Если у вас есть глубоко вложенное состояние, подумайте, Можно ли реструктурировать дочерние объекты, чтобы сидеть в корне. Это упрощает обновление данных.
- есть много удобных библиотек, доступных для обработки неизменяемом состоянии перечисленные в Redux docs. Я рекомендую Иммер, поскольку он позволяет писать код в мутационной форме, но регулирует необходимые клонирование за кадром. Он также замораживает полученный результат объект, чтобы вы не могли случайно мутировать его позже.
чтобы сделать вещи общими, я работал над ответами @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); }
Примечание: указанные выше методы не будут работать для массивов
мы используем 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 вы обрабатываете свое состояние как "нормальный объект". Волшебство происходит за сценой с прокси-объектами.
Я обнаружил, что это работает для меня, имея форму проекта в моем случае, где, например, у вас есть идентификатор и имя, и я бы предпочел поддерживать состояние для вложенного проекта.
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> )
Дайте мне знать!
что-то вроде этого может быть достаточно,
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)