Вопрос по javascript, reactjs – this.setState не объединяет состояния, как я ожидал

146

У меня есть следующее состояние:

this.setState({ selected: { id: 1, name: 'Foobar' } });  

Затем я обновляю состояние:

this.setState({ selected: { name: 'Barfoo' }});

Поскольку setState предполагает слияние, я ожидаю, что оно будет:

{ selected: { id: 1, name: 'Barfoo' } }; 

Но вместо этого он ест идентификатор и состояние:

{ selected: { name: 'Barfoo' } }; 

Это ожидаемое поведение и что?Есть ли решение обновить только одно свойство вложенного объекта состояния?

Ваш Ответ

13   ответов
7

Lodash пример:

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"}
    }
});
33

для объединения новых данных, я хотел бы отметить, что это может сломаться. Изменения состояния ставятся в очередь и не сразу изменяют компонентгосударственный объект. Поэтому ссылка на данные состояния до обработки очереди даст вам устаревшие данные, которые не отражают ожидающие изменения, сделанные вами в setState. Из документов:

setState () не изменяет немедленно this.state, но создает ожидающий переход в состояние. Доступ к this.state после вызова этого метода может потенциально вернуть существующее значение.

Это означает использованиеток" Состояние в качестве ссылки при последующих вызовах setState ненадежно. Например:

Первый вызов setState, стоящий в очереди изменения состояния объектаВторой вызов setState. Ваше состояние использует вложенные объекты, поэтому вы хотите выполнить слияние. Перед вызовом setState вы получаете объект текущего состояния. Этот объект не отражает вышеуказанные изменения, сделанные при первом вызове setState, потому что он 'До сих пор исходное состояние, которое теперь следует учитывать »несвежий».Выполнить слияние. Результат оригинален »затхлый» состояние плюс новые данные, которые вы только что установили, изменения от начального вызова setState не отражаются. Ваш вызов setState ставит в очередь это второе изменение.Реагирует на очередь процессов. Сначала обрабатывается вызов setState, обновляется состояние. Второй вызов setState обрабатывается, обновляется состояние. Второй setState'Теперь объект s заменил первый, и, поскольку данные, которые вы имели при выполнении этого вызова, устарели, измененные устаревшие данные этого второго вызова перенесли изменения, сделанные в первом вызове, которые потеряны.Когда очередь пуста, React определяет, следует ли визуализировать и т. Д. На этом этапе вы будете визуализировать изменения, сделанные во втором вызове setState, и будет так, как если бы первый вызов setState никогда не происходил.

Если вам нужно использовать текущее состояние (например, для объединения данных во вложенный объект), setState альтернативно принимает функцию в качестве аргумента вместо объекта; функция вызывается после любых предыдущих обновлений состояния и передает состояние в качестве аргумента - так что это может быть использовано для внесения атомарных изменений, гарантирующих уважение предыдущих изменений.

Действительно, больше информации приветствуется. Dennis
@nextgentech "Доступ к старому состоянию перед вызовом setState, чтобы вы могли создать новый объект состояния, никогда не будет проблемой » - вы'ошибаюсь Мы'говорим оперезапись состояние на основеthis.state, который может быть устаревшим (или, скорее, в ожидании обновления в очереди) при доступе к нему. Madbreaks
Есть ли пример или несколько документов о том, как это сделать? afaik, никто не принимает это во внимание. Josef.B
@nextgentech " Если вам нужно выполнить сложные операции над состоянием, то сначала скопируйте состояние в новый объект » ТОТ это вся проблема. Ты не можешь "скопировать состояние в новый объект используя, например,let s = this.stateпотому что к этому моменту он уже может устареть. Не мое мнение, задокументированный факт. Вот почемуsetState предполагается, что передается функция, если / когда вам нужно получить доступ к текущему актуальному состоянию. Madbreaks
0

setState пока ожидает, что победитне может быть обновлениями для участников состояния на месте. Вы должны либо скопировать вложенные объекты / массивы самостоятельно (с помощью array.slice или Object.assign), либо использовать выделенную библиотеку.

Как этот.NestedLink напрямую поддерживает обработку составного состояния React.

this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' );

Также ссылка наselected или жеselected.name может быть передан везде как одна опора и изменен там.set

9

не хочу устанавливать другую библиотеку, поэтому здесьЭто еще одно решение.

Вместо:

this.setState({ selected: { name: 'Barfoo' }});

Сделайте это вместо этого:

var newSelected = Object.assign({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });

Или, благодаря @ icc97 в комментариях, еще более лаконично, но, возможно, менее читабельно:

this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });

Кроме того, чтобы быть ясно, этот ответ нене нарушать ни одну из упомянутых выше проблем @bgannonpl.

@JustusRomijn К сожалению, тогда выбуду изменять текущее состояние напрямую.Object.assign(a, b) принимает атрибуты отbвставьте / перезаписать их вa а затем возвращаетсяa, Реагировать на людей можно, если вы измените состояние напрямую. Ryan Shillington
@ icc97 Отлично! Я добавил это к своему ответу. Ryan Shillington
Upvote, проверьте. Просто интересно, почему бы не сделать это так?this.setState({ selected: Object.assign(this.state.selected, { name: "Barfoo" })); Justus Romijn
Именно так. Просто отметьте, что это работает только для мелкого копирования / назначения. Madbreaks
@JustusRomijn Вы можете сделать это согласноMDN назначить в одну строку, если вы добавите{} в качестве первого аргумента:this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });, Это не изменяет исходное состояние. icc97
2

Если следующее состояние зависит от предыдущего состояния, мы рекомендуем использовать вместо него форму функции обновления:

согласно документацииhttps://reactjs.org/docs/react-component.html#setstate, с помощью:

this.setState((prevState) => {
    return {quantity: prevState.quantity + 1};
});
Спасибо за ссылку / ссылку, которая отсутствовала в других ответах. Madbreaks
1

и в итоге у меня появилось несколько сложных объектов в верхнем состоянии, и я пытался сделать мой основной компонент более модульным, поэтому я создал простую оболочку класса, чтобы сохранить состояние верхнего компонента, но с учетом большей локальной логики. ,

Класс-оболочка принимает в качестве конструктора функцию, которая устанавливает свойство основного состояния компонента.

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>
            </this.foo2.component></this.foo.component></div>
        )
    }

    onClickFoo = () => this.foo2.setState({foo: "foo changed foo2!"})
}

Кажется, для моих целей это работает очень хорошо, имейте в виду, что вы можетеt изменить состояние свойств, которые вы назначаете для обернутых компонентов на компоненте верхнего уровня, поскольку каждый обернутый компонент отслеживает свое собственное состояние, но обновляет состояние на верхнем компоненте каждый раз, когда он изменяется.

1
Решение

синтаксис распространения, Целью было сделать объект без каких-либо ссылок наprevState, чтобыprevState Wouldn»не может быть изменено. Но в моем использовании,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-х ответ на аналогичный вопрос.

-2

Я буду использовать некоторые из моего собственного кода, например:

    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

какой миксин вы используете в своем компоненте? Это не работает для меня Sachin
-2

Я использую переменную tmp для изменения.

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}  Дон»сделать это, даже если это нене дал вам проблем еще. Chris
Здесь tmp.theme = v - состояние мутирования. Так что это не рекомендуемый способ. almoraleslopez
0
Решение ES6

Мы устанавливаем государство изначально

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' } }
134

setState() Безразлично»сделать рекурсивное слияние.

Вы можете использовать значение текущего состоянияthis.state.selected построить новое состояние, а затем вызватьsetState() на что: я

var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });

мы использовали функцию_.extend() функция (из библиотеки underscore.js) здесь, чтобы предотвратить изменение существующегоselected часть государства, создав его неглубокую копию.

Другое решение было бы написатьsetStateRecursively() который выполняет рекурсивное слияние в новом состоянии, а затем вызываетreplaceState() с этим:

setStateRecursively: function(stateUpdate, callback) {
  var newState = mergeStateRecursively(this.state, stateUpdate);
  this.replaceState(newState, callback);
}
@Pickels, что такое "_extends» в твоем примере? Безразлично»не похоже на JS встроенный. Edward D'Souza
Для людей, которые предпочитают использовать удлинитель, и которые также используют babel, вы можете сделатьthis.setState({ selected: { ...this.state.selected, name: 'barfoo' } }) который переводится наthis.setState({ selected: _extends({}, this.state.selected, { name: 'barfoo' }) }); Pickels
@EdwardD»Souza It 's библиотека lodash. Это'обычно вызывается как подчеркивание, так же, как jQuery обычно вызывается как знак доллара. (Так что's _.extend ().) mjk
Если вы собираетесь использовать подчеркивание / lodash, вы можете посмотреть на _.merge:stackoverflow.com/questions/19965844/... Sigfried
2

как указано в другом ответе,помощники неизменности.

Поскольку установка состояния в глубину - обычная ситуация, ямы создали следующий миксин:

var SeStateInDepthMixin = {
   setStateInDepth: function(updatePath) {
       this.setState(React.addons.update(this.state, updatePath););
   }
};

Этот миксин входит в большинство моих компонентов, и я обычно не используюsetState прямо больше.

С этим миксином все, что вам нужно сделать для достижения желаемого эффекта, это вызвать функциюsetStateinDepth следующим образом:

setStateInDepth({ selected: { name: { $set: 'Barfoo' }}})

Для дополнительной информации:

О том, как работают миксины в React, смотритеофициальная документацияСинтаксис параметра передается вsetStateinDepth увидеть помощников неизменяемостидокументация.
да, это то, что я ожидал. Я'Я пойду еще на это завтра ... mamruoc
Я нашел решение. Я инициировалthis.state.testкак массив. Перейдя на Объекты, решил это. mamruoc
Извините за поздний ответ, но ваш пример Mixin кажется интересным. Однако, если у меня есть 2 вложенных состояния, например, this.state.test.one и this.state.test.two. Верно ли, что только один из них будет обновлен, а другой будет удален? Когда я обновлю первый, если второй я в состоянии, он будет удален ... mamruoc
hy @mamruoc,this.state.test.two все еще будет там после обновленияthis.state.test.one с помощьюsetStateInDepth({test: {one: {$set: 'new-value'}}}), Я использую этот код во всех своих компонентах, чтобы обойти React 'с ограниченнымsetState функция. Dorian
94

поэтому теперь вы можете делать что-то вроде:

var newState = React.addons.update(this.state, {
  selected: {
    name: { $set: 'Barfoo' }
  }
});
this.setState(newState);

Документация помощников по неизменности:http://facebook.github.io/react/docs/update.html

обновление устарело.facebook.github.io/react/docs/update.html thedanotto
@thedanotto Похоже,React.addons.update() метод устарел, но библиотека заменыgithub.com/kolodny/immutability-helper содержит эквивалентupdate() функция. Bungle

Похожие вопросы