У меня следующее состояние:
this.setState({ selected: { id: 1, name: 'Foobar' } });
Затем я обновляю состояние:
this.setState({ selected: { name: 'Barfoo' }});
Так как setState предполагается слить, я бы ожидал, что это будет:
{ selected: { id: 1, name: 'Barfoo' } };
Но вместо этого он ест идентификатор и состояние:
{ selected: { name: 'Barfoo' } };
Является ли это ожидаемым поведением и какое решение обновляет только одно свойство вложенного объекта состояния?
Я думаю, что setState()
не выполняет рекурсивное слияние.
Вы можете использовать значение текущего состояния this.state.selected
для построения нового состояния, а затем вызвать setState()
на этом:
var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
Я использовал функцию function _.extend()
(из библиотеки underscore.js) здесь, чтобы предотвратить модификацию существующей части selected
состояния, создав ее мелкую копию.
Другим решением было бы написать setStateRecursively()
, который рекурсивный слияние в новом состоянии, а затем вызывает с ним replaceState()
:
setStateRecursively: function(stateUpdate, callback) {
var newState = mergeStateRecursively(this.state, stateUpdate);
this.replaceState(newState, callback);
}
Недавно были добавлены помощники по неизменности в React.addons, поэтому теперь вы можете сделать что-то вроде:
var newState = React.addons.update(this.state, {
selected: {
name: { $set: 'Barfoo' }
}
});
this.setState(newState);
Документация помощников по неизменности: http://facebook.github.io/react/docs/update.html
React.addons.update()
устарел, но библиотека замены github.com/kolodny/immutability-helper содержит эквивалентную функцию update()
.
Поскольку многие ответы используют текущее состояние в качестве основы для слияния новых данных, я хотел бы указать, что это может сломаться. Изменения состояния ставятся в очередь и не сразу изменяют объект состояния компонента. Таким образом, привязка данных состояния до обработки очереди даст вам устаревшие данные, которые не отражают ожидающие изменения, внесенные вами в setState. Из документов:
setState() не сразу мутирует this.state, но создает ожидающий переход состояния. Доступ к this.state после вызова этого метода может потенциально вернуть существующее значение.
Это означает, что использование "текущего" состояния в качестве ссылки при последующих вызовах setState является ненадежным. Например:
Если вам нужно использовать текущее состояние (например, чтобы объединить данные во вложенный объект), setState альтернативно принимает функцию как аргумент вместо объекта; функция вызывается после любых предыдущих обновлений состояния и передает состояние в качестве аргумента, поэтому это может быть использовано для гарантированного соблюдения атомных изменений предыдущих изменений.
Я не хотел устанавливать другую библиотеку, так что вот еще одно решение.
Вместо:
this.setState({ selected: { name: 'Barfoo' }});
Сделайте это вместо этого:
var newSelected = Object.assign({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
Или, благодаря @ccc97 в комментариях, еще более кратко, но, возможно, менее читабельно:
this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });
Кроме того, чтобы быть ясным, этот ответ не нарушает ни одной из проблем, упомянутых выше @bgannonpl.
this.setState({ selected: Object.assign(this.state.selected, { name: "Barfoo" }));
Object.assign(a, b)
берет атрибуты из b
, вставляет / перезаписывает их в a
и затем возвращает a
. Реагировать на людей можно, если вы измените состояние напрямую.
Сохранение предыдущего состояния на основе ответа @bgannonpl:
Пример Лодаша:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }));
Чтобы убедиться, что все работает правильно, вы можете использовать функцию обратного вызова второго параметра:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }), () => alert(this.state.selected));
Я использовал merge
потому что extend
отклоняет другие свойства в противном случае.
Приведите пример неизменности:
import update from "react-addons-update";
this.setState((previousState) => update(previousState, {
selected:
{
name: {$set: "Barfood"}
}
});
Как сейчас,
Если следующее состояние зависит от предыдущего состояния, мы рекомендуем использовать вместо этого:
согласно документации https://reactjs.org/docs/react-component.html#setstate, используя:
this.setState((prevState) => {
return {quantity: prevState.quantity + 1};
});
Мое решение для такого рода ситуаций заключается в том, чтобы использовать, как еще один ответ, помощники по неизменности.
Поскольку настройка состояния по глубине является обычной ситуацией, я создал следующий mixin:
var SeStateInDepthMixin = {
setStateInDepth: function(updatePath) {
this.setState(React.addons.update(this.state, updatePath););
}
};
Этот mixin включен в большинство моих компонентов, и я обычно больше не использую setState
.
С помощью этого mixin все, что вам нужно сделать для достижения желаемого эффекта, - это вызвать функцию setStateinDepth
следующим образом:
setStateInDepth({ selected: { name: { $set: 'Barfoo' }}})
Для получения дополнительной информации:
setStateinDepth
, см. документацию .this.state.test.two
все еще будет там после того, как вы обновите this.state.test.one
с помощью setStateInDepth({test: {one: {$set: 'new-value'}}})
. Я использую этот код во всех своих компонентах, чтобы обойти ограниченную функцию setState
.
Изменение: Это решение используется для использования синтаксиса распространения. Цель состояла в том, чтобы сделать объект без каких-либо ссылок на prevState
, чтобы prevState
не был изменен. Но в моем использовании prevState
иногда prevState
. Итак, для идеального клонирования без побочных эффектов мы теперь конвертируем prevState
в JSON, а затем снова возвращаемся. (Вдохновение использовать JSON пришло из MDN.)
Помните:
setState(prevState => stateChange)
меры
state
которого вы хотите изменить.Шаги 3 и 4 можно объединить в одну строку.
this.setState(prevState => {
var newSelected = JSON.parse(JSON.stringify(prevState.selected)) //1
newSelected.name = 'Barfoo'; //2
var update = { selected: newSelected }; //3
return update; //4
});
this.setState(prevState => {
var selected = JSON.parse(JSON.stringify(prevState.selected)) //1
selected.name = 'Barfoo'; //2
return { selected }; //3, 4
});
Это хорошо следует рекомендациям React. На основании ответа eicksl на аналогичный вопрос.
Я использую классы es6, и я закончил с несколькими сложными объектами в моем верхнем состоянии и пытался сделать мой основной компонент более модульным, поэтому я создал простую оболочку класса, чтобы сохранить состояние на верхнем компоненте, но разрешить более локальная логика.
Класс wrapper принимает функцию как свой конструктор, который устанавливает свойство в состоянии основного компонента.
export default class StateWrapper {
constructor(setState, initialProps = []) {
this.setState = props => {
this.state = {...this.state, ...props}
setState(this.state)
}
this.props = initialProps
}
render() {
return(<div>render() not defined</div>)
}
component = props => {
this.props = {...this.props, ...props}
return this.render()
}
}
Затем для каждого сложного свойства в верхнем состоянии я создаю один класс StateWrapped. Здесь вы можете установить реквизиты по умолчанию в конструкторе, и они будут установлены, когда класс инициализирован, вы можете ссылаться на локальное состояние для значений и устанавливать локальное состояние, ссылаться на локальные функции и передавать ли он цепочку:
class WrappedFoo extends StateWrapper {
constructor(...props) {
super(...props)
this.state = {foo: "bar"}
}
render = () => <div onClick={this.props.onClick||this.onClick}>{this.state.foo}</div>
onClick = () => this.setState({foo: "baz"})
}
Итак, моему компоненту верхнего уровня нужен только конструктор, чтобы установить для каждого класса его свойство состояния верхнего уровня, простой рендеринг и любые функции, которые связывают кросс-компонент.
class TopComponent extends React.Component {
constructor(...props) {
super(...props)
this.foo = new WrappedFoo(
props => this.setState({
fooProps: props
})
)
this.foo2 = new WrappedFoo(
props => this.setState({
foo2Props: props
})
)
this.state = {
fooProps: this.foo.state,
foo2Props: this.foo.state,
}
}
render() {
return(
<div>
<this.foo.component onClick={this.onClickFoo} />
<this.foo2.component />
</div>
)
}
onClickFoo = () => this.foo2.setState({foo: "foo changed foo2!"})
}
Кажется, что я хорошо работаю для своих целей, помните, что вы не можете изменить состояние свойств, которые вы назначаете для завернутых компонентов на компоненте верхнего уровня, поскольку каждый завернутый компонент отслеживает свое собственное состояние, но обновляет состояние на верхний компонент каждый раз, когда он изменяется.
Мы устанавливаем государство изначально
this.setState({ selected: { id: 1, name: 'Foobar' } });
//this.state: { selected: { id: 1, name: 'Foobar' } }
Мы изменяем свойство на некотором уровне объекта состояния:
const { selected: _selected } = this.state
const selected = { ..._selected, name: 'Barfoo' }
this.setState({selected})
//this.state: { selected: { id: 1, name: 'Barfoo' } }
Реальное состояние не выполняет рекурсивное слияние в setState
, в то время как ожидает, что не будет обновлений состояния на месте в одно и то же время. Вам либо нужно скопировать вложенные объекты/массивы самостоятельно (с помощью array.slice или Object.assign), либо использовать выделенную библиотеку.
Как этот. NestedLink напрямую поддерживает обработку составного состояния реакции.
this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' );
Кроме того, ссылка на selected
или selected.name
может быть передана повсюду в качестве единственной опоры и изменена там с помощью set
.
Я использую tmp var для изменения.
changeTheme(v) {
let tmp = this.state.tableData
tmp.theme = v
this.setState({
tableData : tmp
})
}
let original = {a:1}; let tmp = original; tmp.a = 5; // original is now === Object {a: 5}
Не делайте этого, даже если он еще не доставил вам проблем.
Установили ли вы начальное состояние?
Я использую некоторые из своих собственных кодов, например:
getInitialState: function () {
return {
dragPosition: {
top : 0,
left : 0
},
editValue : "",
dragging : false,
editing : false
};
}
В приложении, над которым я работаю, это то, как я устанавливал и использовал состояние. Я верю, что в setState
вы можете просто редактировать любые состояния, которые вы хотите индивидуально, я вызывал их так:
onChange: function (event) {
event.preventDefault();
event.stopPropagation();
this.setState({editValue: event.target.value});
},
Имейте в виду, что вы должны установить состояние в функции React.createClass
, которую вы назвали getInitialState
this.setState({ selected: { ...this.state.selected, name: 'barfoo' } })
который переводится вthis.setState({ selected: _extends({}, this.state.selected, { name: 'barfoo' }) });