Вопрос:

this.setState не объединяет состояния, как я ожидал

javascript reactjs

64246 просмотра

13 ответа

22125 Репутация автора

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

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

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

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

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

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

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

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

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

Автор: Pickels Источник Размещён: 21.09.2013 02:50

Ответы (13)


138 плюса

5127 Репутация автора

Решение

Я думаю, setState()что не делает рекурсивное слияние.

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

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

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

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

setStateRecursively: function(stateUpdate, callback) {
  var newState = mergeStateRecursively(this.state, stateUpdate);
  this.replaceState(newState, callback);
}
Автор: andreypopp Размещён: 21.09.2013 03:17

-2 плюса

556 Репутация автора

Вы установили начальное состояние?

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

    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

Автор: asherrard Размещён: 22.11.2013 10:29

95 плюса

959 Репутация автора

Помощники неизменности были недавно добавлены в React.addons, поэтому теперь вы можете делать что-то вроде:

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

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

Автор: user3874611 Размещён: 07.09.2014 06:21

-3 плюса

59 Репутация автора

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

changeTheme(v) {
    let tmp = this.state.tableData
    tmp.theme = v
    this.setState({
        tableData : tmp
    })
}
Автор: hkongm Размещён: 06.07.2015 09:34

2 плюса

1914 Репутация автора

Мое решение для такой ситуации - использовать, как указано в другом ответе, помощников по неизменности .

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

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

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

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

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

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

Автор: Dorian Размещён: 30.10.2015 08:38

34 плюса

376 Репутация автора

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

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

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

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

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

Автор: bgannonpl Размещён: 16.12.2015 08:14

1 плюс

0 Репутация автора

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

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

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!"})
}

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

Автор: user1641172 Размещён: 08.05.2016 02:37

7 плюса

4390 Репутация автора

Сохранение предыдущего состояния на основе ответа @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"}
    }
});
Автор: Martin Dawson Размещён: 23.08.2016 06:12

0 плюса

1686 Репутация автора

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

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

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

Кроме того, ссылка на selectedили selected.nameможет быть передана повсеместно в виде единого объекта и изменена там с помощью set.

Автор: gaperton Размещён: 19.04.2017 07:58

10 плюса

7042 Репутация автора

Я не хотел устанавливать другую библиотеку, так что вот еще одно решение.

Вместо:

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.

Автор: Ryan Shillington Размещён: 21.07.2017 07:13

2 плюса

1260 Репутация автора

На данный момент,

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

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

this.setState((prevState) => {
    return {quantity: prevState.quantity + 1};
});
Автор: egidiocs Размещён: 26.11.2017 01:10

1 плюс

71 Репутация автора

Решение

Изменить: это решение используется для использования синтаксиса распространения . Цель состояла в том, чтобы сделать объект без каких-либо ссылок на него prevState, чтобы prevStateего нельзя было изменить. Но в моем использовании, prevStateказалось, иногда модифицируется. Итак, для идеального клонирования без побочных эффектов, теперь мы конвертируем prevStateв JSON, а затем снова. (Вдохновение использовать JSON пришло из MDN .)

Помните:

меры

  1. Сделайте копию собственности корневого уровня в stateтом , что вы хотите изменить
  2. Мутировать этот новый объект
  3. Создать объект обновления
  4. Вернуть обновление

Шаги 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 на аналогичный вопрос.

Автор: Tim Размещён: 30.07.2018 06:36

1 плюс

4125 Репутация автора

Решение 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' } }
Автор: Roman Размещён: 19.12.2018 11:22
Вопросы из категории :
32x32