IT

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

lottoking 2020. 6. 10. 08:01
반응형

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


다음과 같은 상태입니다.

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

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

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

setState는 병합한다고 가정하므로 다음과 같습니다.

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

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

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

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


나는 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);
}

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

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

불변 헬퍼 문서 .


많은 답변이 현재 상태를 새 데이터를 병합하기위한 기초로 사용하기 때문에 이것이 깨질 수 있음을 지적하고 싶었습니다. 상태 변경이 대기 중이며 구성 요소의 상태 객체를 즉시 수정하지는 않습니다. 대기열이 처리되기 전에 상태 데이터를 참조하면 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는 객체 대신 인수로 함수를 사용할 수도 있습니다. 이 함수는 이전에 상태를 업데이트 한 후 호출되고 상태를 인수로 전달하므로 이전 변경을 존중하도록 원자 적 변경을 보장하는 데 사용할 수 있습니다.


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

대신에:

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


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

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

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

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

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

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

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

자세한 내용은:

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

현재로서는

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

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

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

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()
    }
}

Then for each complex property on the top state, i create one StateWrapped class. You can set the default props in the constructor here and they will be set when the class is initialised, you can refer to the local state for values and set the local state, refer to local functions, and have it passed up the chain:

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


}

So then my top level component just needs the constructor to set each class to it's top level state property, a simple render, and any functions that communicate cross-component.

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

Seems to work quite well for my purposes, bear in mind though you can't change the state of the properties you assign to wrapped components at the top level component as each wrapped component is tracking its own state but updating the state on the top component each time it changes.


Solution

Edit: This solution used to use spread syntax. The goal was make an object without any references to prevState, so that prevState wouldn't be modified. But in my usage, prevState appeared to be modified sometimes. So, for perfect cloning without side effects, we now convert prevState to JSON, and then back again. (Inspiration to use JSON came from MDN.)

Remember:

Steps

  1. Make a copy of the root-level property of state that you want to change
  2. Mutate this new object
  3. Create an update object
  4. Return the update

Steps 3 and 4 can be combined on one line.

Example

this.setState(prevState => {
    var newSelected = JSON.parse(JSON.stringify(prevState.selected)) //1
    newSelected.name = 'Barfoo'; //2
    var update = { selected: newSelected }; //3
    return update; //4
});

Simplified example:

this.setState(prevState => {
    var selected = JSON.parse(JSON.stringify(prevState.selected)) //1
    selected.name = 'Barfoo'; //2
    return { selected }; //3, 4
});

This follows the React guidelines nicely. Based on eicksl's answer to a similar question.


ES6 solution

We set the state initially

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

We are changeing a property on some level of the state object:

const { selected: _selected } = this.state
const  selected = { ..._selected, name: 'Barfoo' }
this.setState({selected})
//this.state: { selected: { id: 1, name: 'Barfoo' } }

React state doesn't perform the recursive merge in setState while expects that there won't be in-place state member updates at the same time. You either have to copy enclosed objects/arrays yourself (with array.slice or Object.assign) or use the dedicated library.

Like this one. NestedLink directly supports handling of the compound React state.

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

Also, the link to the selected or selected.name can be passed everywhere as a single prop and modified there with set.


have you set the initial state?

I'll use some of my own code for example:

    getInitialState: function () {
        return {
            dragPosition: {
                top  : 0,
                left : 0
            },
            editValue : "",
            dragging  : false,
            editing   : false
        };
    }

In an app I'm working on, this is how I've been setting and using state. I believe on setState you can then just edit whatever states you want individually I've been calling it like so:

    onChange: function (event) {
        event.preventDefault();
        event.stopPropagation();
        this.setState({editValue: event.target.value});
    },

Keep in mind you have to set the state within the React.createClass function that you called getInitialState


I use the tmp var to change.

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

참고URL : https://stackoverflow.com/questions/18933985/this-setstate-isnt-merging-states-as-i-would-expect

반응형