[javascript] 반응 : setState를 사용하여 state.item [1]을 상태로 업데이트하는 방법은 무엇입니까?

사용자가 자신의 양식을 디자인 할 수있는 앱을 만들고 있습니다. 예를 들어 필드 이름과 포함해야 할 다른 열의 세부 사항을 지정하십시오.

컴포넌트는 여기 에서 JSFiddle로 사용 가능 합니다 .

내 초기 상태는 다음과 같습니다.

var DynamicForm = React.createClass({
  getInitialState: function() {
   var items = {};
   items[1] = { name: 'field 1', populate_at: 'web_start',
                same_as: 'customer_name',
                autocomplete_from: 'customer_name', title: '' };
   items[2] = { name: 'field 2', populate_at: 'web_end',
                same_as: 'user_name',
                    autocomplete_from: 'user_name', title: '' };

     return { items };
   },

  render: function() {
     var _this = this;
     return (
       <div>
         { Object.keys(this.state.items).map(function (key) {
           var item = _this.state.items[key];
           return (
             <div>
               <PopulateAtCheckboxes this={this}
                 checked={item.populate_at} id={key}
                   populate_at={data.populate_at} />
            </div>
            );
        }, this)}
        <button onClick={this.newFieldEntry}>Create a new field</button>
        <button onClick={this.saveAndContinue}>Save and Continue</button>
      </div>
    );
  }

사용자가 값을 변경할 때 상태를 업데이트하고 싶지만 올바른 객체를 타겟팅하기가 어렵습니다.

var PopulateAtCheckboxes = React.createClass({
  handleChange: function (e) {
     item = this.state.items[1];
     item.name = 'newName';
     items[1] = item;
     this.setState({items: items});
  },
  render: function() {
    var populateAtCheckbox = this.props.populate_at.map(function(value) {
      return (
        <label for={value}>
          <input type="radio" name={'populate_at'+this.props.id} value={value}
            onChange={this.handleChange} checked={this.props.checked == value}
            ref="populate-at"/>
          {value}
        </label>
      );
    }, this);
    return (
      <div className="populate-at-checkboxes">
        {populateAtCheckbox}
      </div>
    );
  }
});

this.setState업데이트하려면 어떻게해야 items[1].name합니까?



답변

update불변 도우미를 사용할 수 있습니다 .

this.setState({
  items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})

또는을 shouldComponentUpdate()사용하여 수명주기 방법 에서이 항목의 변경 사항을 감지하지 못하는 경우 ===상태를 직접 편집하고 구성 요소를 다시 렌더링하도록 할 수 있습니다. 이는 @limelights의 답변과 동일합니다. 객체를 상태에서 벗어나 편집합니다.

this.state.items[1].name = 'updated field name'
this.forceUpdate()

편집 후 추가 :

콜백 함수를 상태 유지 상위에서 상태 변경을 트리거해야하는 하위 컴포넌트로 전달하는 방법에 대한 예제는 반응 학습에서 단순 컴포넌트 통신 학습을 확인하십시오 .


답변

이 스레드에는 많은 잘못된 정보가 있으므로 도우미 라이브러리없이 수행 할 수있는 방법은 다음과 같습니다.

handleChange: function (e) {
    // 1. Make a shallow copy of the items
    let items = [...this.state.items];
    // 2. Make a shallow copy of the item you want to mutate
    let item = {...items[1]};
    // 3. Replace the property you're intested in
    item.name = 'newName';
    // 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
    items[1] = item;
    // 5. Set the state to our new copy
    this.setState({items});
},

원하는 경우 2 단계와 3 단계를 결합 할 수 있습니다.

let item = {
    ...items[1],
    name: 'newName'
}

또는 한 줄로 모든 것을 할 수 있습니다.

this.setState(({items}) => ({
    items: [
        ...items.slice(0,1),
        {
            ...items[1],
            name: 'newName',
        },
        ...items.slice(2)
    ]
}));

참고 : items배열을 만들었습니다 . OP는 개체를 사용했습니다. 그러나 개념은 동일합니다.


터미널 / 콘솔에서 진행중인 작업을 확인할 수 있습니다.

 node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied


답변

잘못된 방법!

handleChange = (e) => {
    const { items } = this.state;
    items[1].name = e.target.value;

    // update state
    this.setState({
        items,
    });
};

의견에서 많은 우수한 개발자가 지적한 것처럼 상태를 변경하는 것은 잘못되었습니다!

이것을 알아내는 데 시간이 걸렸습니다. 위는 작동하지만 React의 힘을 빼앗습니다. 예를 들어 componentDidUpdate직접 수정 되었기 때문에이 업데이트를 업데이트로 볼 수 없습니다.

그래서에게 올바른 방법은 다음과 같습니다

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [prevState.items[1].name]: e.target.value,
        },
    }));
};


답변

바닐라 자바 스크립트의 : 일반적으로 세 가지 방법이 사용의 상태, 반작용에 깊이 중첩 된 객체 / 변수를 수정하려면 Object.assign, 불변성-도우미cloneDeepLodash을 .

이것을 달성하기 위해 덜 인기있는 다른 타사 라이브러리도 많이 있지만이 답변에서는이 세 가지 옵션 만 다룰 것입니다. 또한 배열 확산과 같은 일부 추가 바닐라 JavaScript 메소드가 존재하지만 (예 : @mpen의 답변 참조) 직관적이지 않고 사용하기 쉽고 모든 상태 조작 상황을 처리 할 수 ​​없습니다.

답변에 대한 최고 투표 의견에서 셀 수없이 많은 시간이 지적 되었 듯이, 저자는 국가의 직접적인 돌연변이를 제안 합니다 . 이것은 유비쿼터스 리 액트 안티 패턴으로 필연적으로 원치 않는 결과를 초래합니다. 올바른 방법을 배우십시오.

널리 사용되는 세 가지 방법을 비교해 봅시다.

이 상태 객체 구조가 주어지면 :

state = {
    outer: {
        inner: 'initial value'
    }
}

다음 방법을 사용하여 inner나머지 상태에 영향을주지 않으면 서 가장 안쪽 필드의 값 을 업데이트 할 수 있습니다 .

1. 바닐라 JavaScript의 Object.assign

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the shallow copying:', outer.inner) // initial value
    const newOuter = Object.assign({}, outer, { inner: 'updated value' })
    console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

<main id="react"></main>

것을 명심 Object.assign이 깊은 복제를 수행하지 않습니다 , 이후 그것은 단지 복사 속성 값 과의 그 이유는 그것이라고 무엇을 복사 얕은을 (주석 참조).

이것이 작동하기 위해서는 기본 유형 ( outer.inner) 의 속성 , 즉 문자열, 숫자, 부울 만 조작해야 합니다.

이 예제에서, 우리는 새로운 상수 (만들 const newOuter...사용) Object.assign(빈 객체를 생성하는 {}), 복사 outer(객체 { inner: 'initial value' }그것으로) 한 후 사본을 다른 개체 { inner: 'updated value' } 이상 이.

이런 식으로 결국 새로 생성 된 newOuter상수는 속성이 재정 의 된 { inner: 'updated value' }이후의 값을 유지합니다 inner. 이는 newOuter필요에 따라이 변이 될 수 있으며, 상태가 동일하고 달렸다이다 업데이트 명령 할 때까지 변경되지 남아있을 것입니다 때문에, 상태에있는 개체에 연결되지 않는 새로운 객체입니다.

마지막 부분은 setOuter()setter 를 사용 outer하여 상태 의 원본 을 새로 만든 newOuter객체로 바꾸는 것입니다 (값만 변경되고 속성 이름 outer은 변경 되지 않음).

이제보다 깊은 상태를 상상해보십시오 state = { outer: { inner: { innerMost: 'initial value' } } }. newOuter객체 를 만들어 outer상태 의 내용으로 채울 수는 있지만 너무 깊게 중첩되어 있기 때문에 새로 만든 객체에 값을 Object.assign복사 할 수 없습니다 .innerMostnewOuterinnerMost

당신은 여전히 복사 할 수 inner위의 예처럼,하지만 지금은 객체와 이후 되지 원시의 기준 에서 newOuter.inner받는 복사됩니다 outer.inner우리가 지방으로 끝날 것이라는 점을 대신하는 수단 newOuter을 직접 상태에서 객체에 연결된 객체 .

즉,이 경우 로컬에서 생성 된 돌연변이가 실제로 동일한 상태가 되었기 때문에 (상태에서) 객체에 newOuter.inner직접 영향을 미칩니다 outer.inner(컴퓨터의 메모리에서).

Object.assign 따라서 가장 기본적인 멤버가 기본 유형의 값을 보유하는 비교적 단순한 단일 레벨 심층 구조가있는 경우에만 작동합니다.

업데이트해야하는 더 깊은 개체 (2 단계 이상)가있는 경우을 사용하지 마십시오 Object.assign. 상태를 직접 변경할 위험이 있습니다.

2. Lodash의 cloneDeep

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the deep cloning:', outer.inner) // initial value
    const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
    newOuter.inner = 'updated value'
    console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

<main id="react"></main>

Lodash의 cloneDeep 은 사용하기가 더 간단합니다. 딥 클로닝을 수행 하므로 다중 레벨 객체 또는 배열이 상당히 복잡한 상태 인 경우 강력한 옵션입니다. 그냥 cloneDeep()최상위 상태 속성, 당신이, 그리고 제발 어떤 방법으로 복제 된 일부 변이 setOuter()는 상태로 백업을.

3. 불변 헬퍼

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    const update = immutabilityHelper
    console.log('Before the deep cloning and updating:', outer.inner) // initial value
    const newOuter = update(outer, { inner: { $set: 'updated value' } })
    console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper@3.0.0"></script>

<main id="react"></main>

immutability-helper완전히 새로운 수준에 소요되며, 그것에 대해 시원한의 점은 수뿐만 아니라 것입니다 $set상태 항목에 값뿐만 아니라 $push, $splice, $merge(등) 그들. 사용 가능한 명령 목록은 다음과 같습니다 .

사이드 노트

다시 말하지만, 깊게 중첩 된 ( )이 아니라 상태 객체 setOuter첫 번째 수준 속성 ( outer이 예제에서는) 만 수정 한다는 점을 명심 하십시오 outer.inner. 다른 방식으로 행동하면이 질문은 존재하지 않을 것입니다.

프로젝트에 적합한 것은 무엇입니까?

외부 의존성을 원하지 않거나 사용할 수없고 간단한 상태 구조를 갖고 있다면 에 충실하십시오 Object.assign.

거대하고 복잡한 상태조작하는 경우 Lodash cloneDeep가 현명한 선택입니다.

고급 기능 이 필요한 경우 , 즉 상태 구조가 복잡하고 모든 종류의 작업을 수행해야하는 immutability-helper경우 상태 조작에 사용할 수있는 매우 고급 도구입니다.

… 또는 정말로 이것을해야합니까?

복잡한 데이터를 React의 상태로 보유하고 있다면 다른 방법으로 처리하는 것이 좋습니다. React 컴포넌트에서 복잡한 상태 객체를 바로 설정하는 것은 간단한 작업이 아니며 다른 접근 방식에 대해 강력히 제안하는 것이 좋습니다.

복잡한 데이터를 Redux 스토어에 보관하지 않고 리듀서 및 / 또는 sagas를 사용하여 설정하고 선택기를 사용하여 액세스하는 것이 좋습니다.


답변

나는 같은 문제가 있었다. 작동하는 간단한 해결책이 있습니다!

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });


답변

setState 의 React 문서에 따르면 Object.assign다른 답변에서 제안한대로 사용 하는 것이 이상적이지 않습니다. setState의 비동기 동작 특성으로 인해이 기술을 사용한 후속 호출은 이전 호출을 무시하여 원하지 않는 결과를 초래할 수 있습니다.

대신 React 문서 setState는 이전 상태에서 작동 하는 업데이터 양식을 사용하는 것이 좋습니다 . React가 상태 불변성을 보존해야하기 때문에 배열이나 객체 업데이트 할 때 새로운 배열이나 객체반환해야합니다 . ES6 구문의 spread 연산자를 사용하여 배열을 얕게 복사하고 주어진 배열 인덱스에서 객체의 속성을 만들거나 업데이트하면 다음과 같습니다.

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})


답변

먼저 원하는 항목을 가져 와서 해당 개체에서 원하는 것을 변경 한 다음 상태로 다시 설정하십시오. getInitialState키가있는 객체를 사용하면 객체를 전달하여 상태를 사용하는 방법이 훨씬 쉬워집니다.

handleChange: function (e) {
   item = this.state.items[1];
   item.name = 'newName';
   items[1] = item;

   this.setState({items: items});
}