this.setState는 예상대로 상태를 병합하지 않습니다.


160

다음과 같은 상태입니다.

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

그런 다음 상태를 업데이트합니다.

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

때문에 setState병합한다고 가정이다 나는 것으로 기대 :

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

그러나 대신 ID를 먹고 상태는 다음과 같습니다.

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

이것이 예상되는 동작이며 중첩 된 상태 개체의 속성 하나만 업데이트하는 솔루션은 무엇입니까?

답변:


143

나는 생각한다 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);
}

7
이 작업을 두 번 이상 수행하면 반응이 replaceState 호출을 대기시키고 마지막 호출이 이길 가능성이 있습니까?
edoloughlin

111
extend 사용을 선호하고 또한 babel을 사용하는 사람들을 위해 this.setState({ selected: { ...this.state.selected, name: 'barfoo' } })번역 할 수 있습니다this.setState({ selected: _extends({}, this.state.selected, { name: 'barfoo' }) });
Pickels

8
@Pickels 이것은 React에 훌륭하고 멋지게 관용적이며 underscore/ 가 필요하지 않습니다 lodash. 자체 답변으로 돌리십시오.
ericsoco

14
this.state 반드시 최신 정보는 아닙니다 . extend 메소드를 사용하여 예기치 않은 결과를 얻을 수 있습니다. 업데이트 기능을 전달하는 setState것이 올바른 해결책입니다.
Strom

1
@ EdwardD'Souza lodash 라이브러리입니다. jQuery가 일반적으로 달러 기호로 호출되는 것과 마찬가지로 일반적으로 밑줄로 호출됩니다. (따라서 _.extend ()입니다.)
mjk

96

불변 도우미가 최근 React.addons에 추가되었으므로 이제 다음과 같은 작업을 수행 할 수 있습니다.

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

불변 헬퍼 문서 .


7
업데이트가 더 이상 사용되지 않습니다. facebook.github.io/react/docs/update.html
thedanotto

3
@thedanotto React.addons.update()메소드가 더 이상 사용되지 않는 것으로 보이지만 대체 라이브러리 github.com/kolodny/immutability-helper 에는 동등한 update()함수 가 포함되어 있습니다 .
Bungle

37

많은 답변이 현재 상태를 새 데이터를 병합하기위한 기초로 사용하기 때문에 이것이 깨질 수 있음을 지적하고 싶었습니다. 상태 변경이 대기 중이며 구성 요소의 상태 객체를 즉시 수정하지는 않습니다. 대기열이 처리되기 전에 상태 데이터를 참조하면 setState에서 보류중인 변경 사항을 반영하지 않는 오래된 데이터가 제공됩니다. 문서에서 :

setState ()는 this.state를 즉시 변경하지 않지만 보류 상태 전환을 만듭니다. 이 메소드를 호출 한 후 this.state에 액세스하면 기존 값을 리턴 할 수 있습니다.

이는 setState에 대한 후속 호출에서 "현재"상태를 참조로 사용하는 것은 신뢰할 수 없음을 의미합니다. 예를 들면 다음과 같습니다.

  1. 상태 객체에 대한 변경을 큐잉하는 setState에 대한 첫 번째 호출
  2. setState에 대한 두 번째 호출 상태는 중첩 된 객체를 사용하므로 병합을 수행하려고합니다. setState를 호출하기 전에 현재 상태 객체를 얻습니다. 이 객체는 위의 setState를 처음 호출 할 때 대기중인 변경 사항을 반영하지 않습니다. 위의 상태는 여전히 "stale"로 간주되어야하기 때문입니다.
  3. 병합을 수행하십시오. 결과는 원래 "stale"상태와 방금 설정 한 새 데이터이며 초기 setState 호출의 변경 사항은 반영되지 않습니다. setState 호출은이 두 번째 변경을 대기시킵니다.
  4. 프로세스 큐를 반응시킵니다. 첫 번째 setState 호출이 처리되고 상태가 업데이트됩니다. 두 번째 setState 호출이 처리되고 상태가 업데이트됩니다. 두 번째 setState의 객체가 이제 첫 번째 객체를 대체했으며 해당 호출을 수행 할 때 보유한 데이터가 오래 되었기 때문에이 두 번째 호출의 수정 된 오래된 데이터가 첫 번째 호출의 변경 사항을 방해하여 손실되었습니다.
  5. 큐가 비어 있으면 React는 렌더링 여부를 결정합니다.이 시점에서 두 번째 setState 호출에서 작성된 변경 사항을 렌더링하며 첫 번째 setState 호출이 발생한 적이없는 것처럼됩니다.

현재 상태를 사용해야하는 경우 (예 : 데이터를 중첩 된 객체로 병합) setState는 객체 대신 인수로 함수를 사용할 수도 있습니다. 이 함수는 이전에 상태를 업데이트 한 후 호출되고 상태를 인수로 전달하므로 이전 변경을 존중하도록 원자 적 변경을 보장하는 데 사용할 수 있습니다.


5
이 작업을 수행하는 방법에 대한 예제가 더 있습니까? afaik, 아무도 이것을 고려하지 않습니다.
Josef.B

@Dennis 아래 답변을 시도하십시오.
마틴 도슨

문제가 어디에 있는지 알 수 없습니다. 설명하고있는 것은 setState를 호출 한 후 "new"상태에 액세스하려는 경우에만 발생합니다. 그것은 OP 질문과 관련이없는 완전히 다른 문제입니다. 새로운 상태 객체를 생성 할 수 있도록 setState를 호출하기 전에 이전 상태에 액세스하는 것은 결코 문제가되지 않으며 실제로 React가 기대하는 것입니다.
nextgentech

@nextgentech "setState를 호출하기 전에 이전 상태에 액세스하여 새로운 상태 객체를 구성 할 수 있습니다 ." 우리는에 기반하여 상태를 덮어 쓰는 것에 대해 이야기 this.state하고 있습니다.
Madbreaks

1
@Madbreaks Ok, 예. 공식 문서를 다시 읽었을 때 setState의 함수 형식을 사용하여 이전 상태를 기반으로 상태를 안정적으로 업데이트해야한다고 지정되었습니다. 즉, 같은 함수에서 setState를 여러 번 호출하지 않으면 지금까지 문제가 발생하지 않았습니다. 사양을 다시 읽게 한 답변에 감사드립니다.
nextgentech

10

다른 라이브러리를 설치하고 싶지 않았으므로 여기에 또 다른 솔루션이 있습니다.

대신에:

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이 위에서 언급 한 우려 사항을 위반하지 않습니다.


공감하십시오, 확인하십시오. 왜 이런 식으로 궁금하지? this.setState({ selected: Object.assign(this.state.selected, { name: "Barfoo" }));
Justus Romijn

1
@JustusRomijn 불행히도 현재 상태를 직접 수정하게 될 것입니다. Object.assign(a, b)에서 속성을 가져 와서 b삽입 / 덮어 쓰기 a한 다음를 반환합니다 a. 상태를 직접 수정하면 사람들이 화를냅니다.
라이언 실링

바로 그거죠. 얕은 복사 / 할당에만 작동합니다.
Madbreaks

1
@JustusRomijn 첫 번째 인수로 추가하면 MDN 에 따라 한 줄에 할당 할 수 있습니다 . 원래 상태는 변경하지 않습니다. {}this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });
icc97

@ icc97 니스! 나는 그것을 내 대답에 추가했다.
Ryan Shillington

8

@bgannonpl 답변을 기반으로 이전 상태 유지 :

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

2

이런 종류의 상황에 대한 나의 해결책은 다른 대답이 지적한 것처럼 불변성 도우미를 사용하는 것 입니다.

상태를 깊이 설정하는 것이 일반적인 상황이므로 다음 믹스 인을 만들었습니다.

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

이 믹스 인은 대부분의 구성 요소에 포함되어 있으며 일반적으로 setState더 이상 직접 사용하지 않습니다 .

이 믹스 인을 사용하여 원하는 효과를 얻으 setStateinDepth려면 다음과 같은 방법으로 함수를 호출하면됩니다 .

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

자세한 내용은:

  • React에서 믹스 인 작동 방식에 대해서는 공식 문서를 참조하십시오 .
  • 전달 된 매개 변수의 구문 setStateinDepth에서 불변성 헬퍼 문서 를 참조하십시오 .

답장이 늦어서 죄송하지만 Mixin 예제가 흥미로워 보입니다. 그러나 2 개의 중첩 된 상태가있는 경우 (예 : this.state.test.one 및 this.state.test.two). 이 중 하나만 업데이트되고 다른 하나만 삭제되는 것이 맞습니까? 내가 첫번째 상태를 업데이트 할 때, 두번째 상태 i가 상태라면, 그것은 제거 될 것입니다.
mamruoc

hy @mamruoc은를 사용하여 this.state.test.two업데이트 한 후에도 계속 유지 this.state.test.one됩니다 setStateInDepth({test: {one: {$set: 'new-value'}}}). React의 제한된 setState기능 을 피하기 위해 모든 구성 요소 에서이 코드를 사용 합니다.
Dorian

1
해결책을 찾았습니다. 나는 this.state.test배열로 시작 했다. 객체로 변경하여 해결했습니다.
mamruoc

2

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

랩핑 된 각 구성 요소가 자체 상태를 추적하지만 최상위 구성 요소의 상태를 업데이트하므로 최상위 구성 요소에서 래핑 된 구성 요소에 할당 한 속성의 상태를 변경할 수는 없지만 변경 될 때마다


2

현재로서는

다음 상태가 이전 상태에 의존하는 경우 대신 업데이터 함수 양식을 사용하는 것이 좋습니다.

https://reactjs.org/docs/react-component.html#setstate 설명서에 따르면 다음을 사용합니다.

this.setState((prevState) => {
    return {quantity: prevState.quantity + 1};
});

다른 답변에서 누락 된 인용 / 링크에 감사드립니다.
Madbreaks

1

해결책

편집 :이 솔루션은 확산 구문 을 사용했습니다 . 목표는에 대한 참조가없는 객체를 만들었 prevState으므로 prevState수정되지 않았습니다. 그러나 내 사용법에서 prevState때로는 수정 된 것처럼 보였습니다. 따라서 부작용없이 완벽한 복제를 위해 이제 prevStateJSON으로 변환 한 다음 다시 돌아갑니다. (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의 답변을 기반으로 합니다.


1

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

0

반응 상태가 재귀 병합을 수행하지 않습니다. setState 동시에 적절한 상태 구성원 업데이트가 없을 것으로 예상합니다. 동봉 된 객체 / 배열을 직접 복사하거나 (array.slice 또는 Object.assign과 함께) 전용 라이브러리를 사용해야합니다.

이 같은. NestedLink 는 복합 반응 상태 처리를 직접 지원합니다.

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

또한 selected또는에 대한 링크 selected.name는 단일 소품으로 어디에서나 전달되어로 수정 될 수 있습니다 set.


-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


1
컴포넌트에서 어떤 믹스 인을 사용하고 있습니까? 이것은 나를 위해 작동하지 않습니다
Sachin

-3

tmp var를 사용하여 변경합니다.

changeTheme(v) {
    let tmp = this.state.tableData
    tmp.theme = v
    this.setState({
        tableData : tmp
    })
}

2
여기서 tmp.theme = v는 변경 상태입니다. 따라서 이것은 권장되는 방법이 아닙니다.
almoraleslopez

1
let original = {a:1}; let tmp = original; tmp.a = 5; // original is now === Object {a: 5} 아직 문제가없는 경우에도이 작업을 수행하지 마십시오.
Chris
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.