[React.JS] 강좌 6-2편 Immutability Helper – State 내부 Array 에 원소 삽입/제거/수정


이 튜토리얼은 2018년에 새로운 강의로 재작성되었습니다 [새 튜토리얼 보기]

이 강의는 조금 오래되었습니다 😉

다음 포스트를 읽으시는 것을 권장 드립니다:


 

 

 

liste

* 이 강좌는 [React.js] 강좌 6-1편 과 이어지는 강좌입니다.

저번 강좌에서 컴포넌트에서 사용할 데이터 배열을 매핑하여 효율적으로 렌더링하는 방법을 배웠습니다. 이번엔 데이터 배열에 변화를 주는 방법을 배워보도록 하겠습니다. 이 과정은 생각보다 쉽지만은 않습니다.

1. state 안의 array 에 원소 삽입/제거/수정

this.state 에 포함된 배열에 원소를 삽입/제거/수정 을 할 때 그 배열에 직접 접근하면 안됩니다.
예를들어, 원소를 추가 할 때 배열객체의 push() 메소드를 사용하면 원하는대로 되지 않습니다.
this.state가 변경된다고해서 컴포넌트가 업데이트되지 않기 때문입니다.
물론 변경 후 React 컴포넌트 APIforceUpdate()를 통하여 컴포넌트가 render()를 다시 실행 하게 끔 하는 방법이 있긴하지만
이건 절대 권장되지 않는 방법입니다.
React 메뉴얼 에서도  this.state를 직접 수정하지 말고 this.setState()를 사용하여 수정 할 것을 강조합니다.
(이 메소드가 실행되면 자동으로 re-rendering 이 진행됩니다.)


1.1 원소 삽입하기

state 내부의 배열에 원소를 추가하는 방법은 다음과 같습니다.

this.setState({
    list: this.state.list.concat(newObj)
})

concat 을 사용함으로서 현재의 list 배열에 newObj 원소를 추가한 새로운 배열을 생성 한 후, 그 값을 현재의 list 로 설정합니다.

배열을 수정 할 땐 원시적인 방법으론 위와 같이 배열 전체를 복사하고 처리 후 기존 값에 덮어씌우는 과정을 거쳐야 합니다.
허나, 만약에 배열의 크기가 클 땐 성능이 좀 저하되겠죠?

다른 방법으로는 Immutability Helpers 를 사용하는 방법이 있습니다.
이는 배열을 더 효율적으로 수정 할 수 있게 해주는 페이스북의 Immutable-js  를 사용합니다.

이를 사용하려면 라이브러리를 사전 설치해주어야 합니다.

React 구버전에서는 해당 라이브러리가 내장되어 import React from 'react/addons'; 으로
React를 import 하여 React.addons.update() 를 사용 할 수 있었으나, 이제 이 방법은 deprecated 되었습니다.
아직도 이렇게 사용은 가능 하나, 브라우저 상에서 ‘react-addon-update’ 를 import 하라고 권장하는 오류 메시지가 발생합니다.

라이브러리 설치 방법

$ npm install --save react-addons-update 를 통하여 라이브러리를 저장 후,

js 파일 상단에 import update from 'react-addons-update' 를 삽입해줍니다.

this.setState({
    list: update(
              this.state.list, 
              {
                  $push: [newObj, newObj2]
              }
});

update() 메소드의 첫 파라미터는 처리 할 배열이며 두번째는 처리 할 명령들을 지니고 있는 객체 타입입니다.

$push: [newObj, newObj2]list 배열에 newObj 와 newObj2 를 추가해줍니다.
한 객체를 추가 할 때도 [ ] 안에 배열형태로 감싸줘야합니다.

Immutable-js 의 syntax 는 MongoDB 쿼리 언어에서 영감을 받았다고 합니다.

브라우저상에서 react-with-addons를 불러와서 사용하는 경우에는 update 가 아닌 React.addons.update 를 사용해야합니다.
(jsfiddle이 이에 해당합니다.)


1.2 원소 제거하기

원소를 제거 할 때 역시, state 의 배열에 직접 접근하면 안 되고, 배열을 복사한 후 원소 제거 후 기존 값에 덮어씌워져야합니다.

JavaScript Array의 내장 함수인 splice()를 사용하면 되지만,
이는 생략하고 더 효율적인 Immutability Helper를 사용하는 예제를 알아보겠습니다.

this.setState({
    list: update(
              this.state.list, 
              {
                  $splice: [[index, 1]]
              }
});

위 코드는 list 배열의 index번째 아이템부터 시작해서 1개의 만큼의 데이터를 제거합니다.

$splice 에 전달 되는 데이터는 배열로 이루어진 배열입니다.


1.3 원소 수정하기

Immutability Helper를 사용하여 특정 원소를 수정하는 예제를 알아보겠습니다.

this.setState({
    list: update(
              this.state.list, 
              {
                  [index]: {
                      field: { $set: "value" },
                      field2: { $set: "value2" }
                  }
              }
});

위 코드는 list 배열의 index 번째 아이템의 field 와 field2 의 값을 변경합니다.


2. 적용하기

자, 위에서 배운것들을 적용해보겠습니다.

앞으로 저희가 만들 클래스는 다음과 같습니다.

  1. ContactCreator: Contact 를 생성하는 컴포넌트
  2. ContactRemover: Contact 를 제거하는 컴포넌트
  3. ContactEditor: Contact를 수정하는 컴포넌트

저희가 앞으로 구현하고자 하는 기능은,  인풋박스에 입력하여 원하는 Contact를 추가 하고,
리스트에 있는 Contact를 선택하여 수정 및 제거를 하는 것 입니다.


2.1 ContactCreator 컴포넌트 만들기

자, 위에서 배운것을 적용하겠습니다.
ContactInfo 를 생성하기 위해 필요한 텍스트박스 두개와 버튼 하나를 지니고있는 컴포넌트를 만들어봅시다.

클래스는 강의 편의상 같은 파일에 작성하도록 하겠습니다.

a. ContactCreator: 클래스 생성 및 렌더링

class ContactCreator extends React.Component {
    render() {
        return (
            <div>
                <p>
                    <input type="text" name="name" placeholder="name"/>
                    <input type="text" name="phone" placeholder="phone"/>
                    <button>
                    Insert
                    </button>
                </p>
            </div>
        );
    }
}

이미지 3

b. Contact: 렌더링 할 컴포넌트에 추가

class Contacts extends React.Component {

/* ... */

    render(){
        return(
            <div>
                <h1>Contacts</h1>
                <ul>
                    {this.state.contactData.map((contact, i) => {
                        return (<ContactInfo name={contact.name}
                                            phone={contact.phone}
                                              key={i}/>);
                    })}
                </ul>
                <ContactCreator/>
            </div>
        );
    }

/* ... */

c. ContactCreator: Input 의 값을 컴포넌트의 state 로 사용하기

class ContactCreator extends React.Component {
    constructor(props) {
        super(props);
        // Configure default state
        this.state = {
            name: "",
            phone: ""
        };
    }

/* ... */
<input type="text" name="name" placeholder="name" value={this.state.name}/>
<input type="text" phone="phone" placeholder="phone" value={this.state.phone}/>

초기 state 값을 지정하고, 렌더링 부분 코드에서 inputvalue를 state를 사용하도록 수정한 후,
인풋박스에 텍스트를 적으려고 시도해보면 값이 고정되서 변경되지 않습니다.
이 부분을 해결하기 위하여, onChange 이벤트를 통하여 인풋박스에 텍스트를 입력 시 status 를 업데이트하도록 설정해야 합니다.

d. ContactCreator: onChange 이벤트 사용하기

class ContactCreator extends React.Component {

/* ... */

    handleChange(e){
        var nextState = {};
        nextState[e.target.name] = e.target.value;
        this.setState(nextState);
    }

/* ... */
<input type="text"
    name="name"
    placeholder="name"
    value={this.state.name}
    onChange={this.handleChange.bind(this)}/>

<input type="text"
    name="phone"
    placeholder="phone"
    value={this.state.phone}
    onChange={this.handleChange.bind(this)}/>

인풋박스의 값을 변경 할 때 실행 될 handleChange(e) 메소드를 만들었습니다.
여기서 파라미터 e 는 JavaScript 의 Event 인터페이스입니다.
e
를 사용함으로서 한 메소드로 여러 인풋박스를 인풋박스의 name 에 따라 처리 할 수 있게됩니다.

렌더링 부분의 코드를 보기 좋게 하기위해 줄바꿈을 하였으며 onChange={this.handleChange.bind(this)}를 넣어주었습니다.
인풋박스가 변경 될 때 해당 메소드를 실행한다는 의미 입니다. bind 를 통하여 컴포넌트의 this 에 접근 할 수 있게 됩니다.

e. ContactCreator: Insert 버튼 기능 구현하기

class ContactCreator extends React.Component {

/* ... */

    handleClick(){
        this.props.onInsert(this.state.name, this.state.phone);
        this.setState({
            name: "",
            phone: ""
        });
    }

/* ... */

<button onClick={this.handleClick.bind(this)}>Insert</button>

/* ... */

버튼을 클릭 했을 때 실행 될 메소드를 만들었습니다.
handleClick() 에서는 parent 컴포넌트인 Contacts 에서 props 로 받아온 메소드를 실행합니다.
그 후, 인풋 박스 값을 비웁니다.

f. Contacts: _insertContact 메소드 만들기

class Contacts extends React.Component {

/* ... */

    _insertContact(name, phone){
        let newState = update(this.state, {
            contactData: {
                $push: [{"name": name, "phone": phone}]
            }
        });
        this.setState(newState);
    }

/* ... */

<ContactCreator onInsert={this._insertContact.bind(this)}/>

/* ... */

Immutability Helpers 를 사용하여 배열에 원소를 추가하였으며,
_insertContact(name, phone) 메소드를 ContactCreator 의 prop 으로 전달 해 주었습니다.

참고: jsfiddle 에선 React.addons.update 를 사용해아햡니다.


2.2 선택 기능 구현하기

배열에서 데이터를 수정 하거나 제거 할 때 필요 할 마우스로 선택하는 기능을 구현해보겠습니다.

a. ContactInfo: handleClick() 메소드 및 onClick prop 추가

class ContactInfo extends React.Component {

    handleClick(){
        this.props.onSelect(this.props.contactKey);
    }

    render() {
        return(
            <li
                onClick={this.handleClick.bind(this)}>
                {this.props.name} {this.props.phone}
            </li>
        );
    }

해당 컴포넌트가 클릭되면 handleClick() 메소드가 실행되며,
이 메소드 내부에선 parent 컴포넌트에서 prop 으로 전달받은 onSelect() 메소드를 실행합니다.

여기서 인수 contactKey 는 해당 컴포넌트의 고유 번호입니다.
컴포넌트를 매핑할 때 key 를 사용하긴 하였으나,
이는 prop으로 간주되지 않으며 React 내부에서 사용하는 용도이기에 직접 접근이 불가합니다.

b. Contacts: _onSelect(), _isSelected() 메소드 추가

class Contacts extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            contactData: [
                /* ... */
            ],
            selectedKey: -1
        };
    }

    /* ... */

    _onSelect(key){
        if(key==this.state.selectedKey){
            console.log("key select cancelled");
            this.setState({
                selectedKey: -1
            });
            return;
        }

        this.setState({
            selectedKey: key
        });
        console.log(key + " is selected");
    }

    _isSelected(key){
        if(this.state.selectedKey == key){
            return true;
        }else{
            return false;
        }
    }

    render() {
        /* ... */
                    {this.state.contactData.map((contact, i) => {
                        return (<ContactInfo name={contact.name}
                                            phone={contact.phone}
                                              key={i}
                                       contactKey={i}
                                       isSelected={this._isSelected.bind(this)(i)}
                                         onSelect={this._onSelect.bind(this)}/>);
                    })}
/* ... */

state selectedKey 는 현재 선택된 컴포넌트의 고유번호 입니다.
만약에 선택된 Contact 가 없을 시에는 -1 로 설정됩니다.

_onSelect() 메소드는 컴포넌트가 클릭 될 때 실행 할 메소드 입니다. 선택 할 컴포넌트가 이미 선택되어있다면 선택을 해제합니다.
이 메소드는 child 컴포넌트의 onSelect prop 으로 전달됩니다.

_isSelect(key) 메소드는 child 컴포넌트에게 해당 컴포넌트가 선택된 상태인지 아닌지 알려줍니다.
이 메소드를 실행 한 결과 값이 child 컴포넌트의 isSelected prop 으로 전달 됩니다.

c. ContactInfo: 렌더링 시 선택된 상태라면 특정 스타일 적용

class ContactInfo extends React.Component {
/* ... */
    render() {

        let getStyle = isSelect => {
            if(!isSelect) return;

            let style = {
                fontWeight: 'bold',
                backgroundColor: '#4efcd8'
            };

            return style;
        };

        return(
            <li style={getStyle(this.props.isSelected)}
                onClick={this.handleClick.bind(this)}>
                {this.props.name} {this.props.phone}
            </li>
            );
    }
}

5번 줄에서는 getStyle 이라는 함수를 선언했습니다. arrow function 이 사용되었는데요, 매개변수가 오직 하나라면 괄호가 생략 될 수 있습니다.

이 함수는 매개변수가 참이면 배경색이 아쿠아색인 스타일을 반환하며 거짓이면 비어있는 스타일을 반환합니다.
강좌 3편 JSX 에서 언급했었던 inline styling이 사용되었습니다.


2.3 ContactRemover 컴포넌트 만들기

선택 기능이 구현 되었으니, 이 컴포넌트를 구현하는건 식은 죽 먹기입니다.

a. ContactRemover: 컴포넌트 작성

class ContactRemover extends React.Component {
    handleClick() {
        this.props.onRemove();
    }

    render() {
        return (
            <button onClick={this.handleClick.bind(this)}>
                Remove selected contact
            </button>
        );
    }
}

버튼이 클릭되면 handleClick() 메소드가 실행 되며, 해당 메소드에선 parent 컴포넌트에서 전달 받은
onRemove() 메소드가 실행됩니다.

b. Contact: 렌더링 할 컴포넌트에 추가

class Contacts extends React.Component {
/* ... */
                <ContactCreator onInsert={this._insertContact.bind(this)}/>
                <ContactRemover/>
/* .. */
}

렌더링 부분에 ContactCreator 하단에 <ContactRemover/> 를 추가하세요.

c. Contact: _removeContact() 메소드 작성

class Contacts extends React.Component {
/* ... */
    _removeContact(){
        if(this.state.selectedKey==-1){
            console.log("contact not selected");
            return;
        }

        this.setState({
            contactData: update(
                this.state.contactData,
                {
                    $splice: [[this.state.selectedKey, 1]]
                }
            ),
            selectedKey: -1
        });
    }
/* ... */

선택한 Contact 를 제거하는 메소드 입니다. 선택된 Contact 가 없다면 작업을 취소합니다.

this.setState(...) 가 실행 되면 state contactData 에서 selectedKey번째 데이터를 제거하고
아무것도 선택하지 않은 상태로 설정합니다.

참고: jsfiddle을 사용한다면 React.addons.update 를 사용해야합니다.

d. Contact: ContactRemover 컴포넌트에 삭제 메소드 prop onRemove 으로 전달

class Contacts extends React.Component {
/* ... */
<ContactRemover onRemove={this._removeContact.bind(this)}/>
/* ... */


2.4 ContactEditor 만들기

ContactEditor 에서 구현하고자 하는 기능은 다음과 같습니다:

  • Contact를 선택하면 Contact 의 name 과 phone 데이터가 인풋박스로 복사됨
  • Edit 버튼을 누르면 Contacts 의 데이터를 수정함.

이 컴포넌트에서 사용 할 prop 들은 다음과 같습니다:

  • isSelected: parent 컴포넌트에서 Contact가 선택 되어있는지 안되어있는지 알려줍니다.
  • onEdit(): parent 컴포넌트에서 전달 받을 메소드로 서, 데이터 수정 작업을 처리합니다.
  • contact: parent 컴포넌트에서 선택된 Contact의 name 과 phone 정보를 갖고있는 객체입니다.

a. ContactEditor: 컴포넌트 초기 작성

이 컴포넌트의 코드 형태는 위에서 만든 ContactCreator 와 매우 비슷합니다.
ContactCreator 를 copy & paste 하고 우선 필요한 부분만 수정하세요.

class ContactEditor extends React.Component {
    constructor(props) {
        super(props);
        // Configure default state
        this.state = {
            name: "",
            phone: ""
        };
    }

    handleClick(){

    }

    handleChange(e){
        var nextState = {};
        nextState[e.target.name] = e.target.value;
        this.setState(nextState);
    }

    render() {
        return (
            <div>
                <p>
                    <input type="text"
                        name="name"
                        placeholder="name"
                        value={this.state.name}
                        onChange={this.handleChange.bind(this)}/>

                    <input type="text"
                        name="phone"
                        placeholder="phone"
                        value={this.state.phone}
                        onChange={this.handleChange.bind(this)}/>
                    <button onClick={this.handleClick.bind(this)}>
                    Edit
                    </button>
                </p>
            </div>
        );
    }
}

코드를 붙여 넣은 후, 클래스 이름을 변경하고 handleClick() 에서 처리할 내용이 다르니 코드를 비워주세요.
이 컴포넌트는 수정하는 컴포넌트니까 버튼 캡션을 Edit 으로 변경해야겠죠?

b. ContactEditor: handleClick() 메소드 작성

class ContactEditor extends React.Component {
/* ... */
    handleClick(){
        if(!this.props.isSelected){
            console.log("contact not selected");

            return;
        }

        this.props.onEdit(this.state.name, this.state.phone);
    }
/* ... */

선택 된 Contact가 없다면 작업을 취소합니다.

onEdit() 은 parent 컴포넌트에서 전달 받을 메소드 입니다.

c. Contacts: _editContact 메소드 초기작성 렌더링 할 컴포넌트 추가

class Contacts extends React.Component {
/* .. */
    _editContact(name, phone){

    }
/* ... */
                <ContactRemover onRemove={this._removeContact.bind(this)}/>
                <ContactEditor onEdit={this._editContact.bind(this)}
                           isSelected={(this.state.selectedKey !=-1)}/>
/* ... */

_editContact() 메소드는 오류가 나지 않도록 초기 작성만하고 구현은 나중에 하도록 하겠습니다.

ContactsRemover 컴포넌트 하단에 <ContactEditor... />를 작성하세요.

prop isSelected 은 JavaScript 표현식을 사용하여 selectedKey가 -1이 아니라면 true를, 맞다면 false를 반환합니다.

d. 선택된 내용을 인풋박스로 복사하는 기능 구현하기

Contact 를 선택 하였을 때 내용을 ContactEditor 의 input 으로 복사되는 기능을 구현해보겠습니다.

일단은 선택된 Contact 의 정보를 ContactEditor 로 전달을 해줘야겠죠 ?

우선 선택된 Contact의 정보를 Contacts 의 state selected 에 저장하도록 합시다.

Contacts:

class Contacts extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            /* ... */
            selectedKey: -1,
            selected: {
                name: "",
                phone: ""
            }
        };
    }

    _onSelect(key){
        if(key==this.state.selectedKey){
            console.log("key select cancelled");
            this.setState({
                selectedKey: -1,
                selected: {
                    name: "",
                    phone: ""
                }
            });
            return;
        }

        this.setState({
            selectedKey: key,
            selected: this.state.contactData[key]
        });
        console.log(key + " is selected");
    }
/* ... */
                <ContactEditor onEdit={this._editContact.bind(this)}
                           isSelected={(this.state.selectedKey !=-1)}
                              contact={this.state.selected}/>
/* ... */

새로운 state 를 사용 할 땐, 언제나 초기 값을 설정해줘야합니다. (그렇지 않으면 오류가 발생하기 쉽상입니다.)

Contact를 선택하였을 때 prop selected 에 값을 저장 하게하고,
선택을 취소 하였을 때, 값을 공백으로 설정하도록 하였습니다.

그리고 이 prop selected 값을 ContactEditor 에 prop contact로 전달해줍니다.

이제 ContactEditor 에서 선택된 Contact의 값을 받아와서 렌더링해줘야겠죠?

하지만, 인풋박스의 value 부분은 유동적이기에 그 부분에 { this.props.contact.name } 을 할 수는 없습니다.

prop값이 바뀔 때마다 state를 업데이트 해줄 필요가 있는데요, 이는 Component Lifecycle API 중 하나인

componentWillReceiveProps()
를 사용하면됩니다. 이 컴포넌트 내장메소드는, prop 값을 받게 될 때 실행되는 메소드입니다.

ContactEditor:

class ContactEditor extends React.Component {
/* ... */
    componentWillReceiveProps(nextProps){
        this.setState({
            name: nextProps.contact.name,
            phone: nextProps.contact.phone
        });
    }
/* ... */

e. Contacts: _editContact 메소드 구현하기

class Contacts extends React.Component {
/* ... */
    _editContact(name, phone){
        this.setState({
            contactData: update(
                this.state.contactData,
                {
                    [this.state.selectedKey]: {
                        name: { $set: name },
                        phone: { $set: phone }
                    }
                }
            ),
            selected: {
                name: name,
                phone: phone
            }
        });
    }
/* ... */

출력물

3. CPU 자원낭비 줄이기

헉.. 강좌의 끝이 아니라니!

저희가 원하고자 하는 기능은 모두 구현하였지만, 사실 저희가 작성한 코드를 CPU 자원을 낭비하고있답니다. (두둥!)

비록 큰데이터를 다루는게 아니기 때문에 성능에 큰 영향을 끼치지는 않고 있지만

코드의 완성도를 위하여 컴포넌트를 최적화해보겠습니다.

3.1 무엇이 문제인가..

데이터가 수정 될 때 마다, 상태에 변동이 없는, 즉 리렌더링 할 필요가 없는 컴포넌트들도 리렌더링이 되고있습니다.

한번 ContactInfo 컴포넌트의 render() 메소드에 코드 console.log("rendered: " + this.props.name); 를 추가해보세요.

ContactInfo:

class ContactInfo extends React.Component {
/* ... */
    render() {
        console.log("rendered: " + this.props.name);
/* ... */

그리고 Contact들을 선택해보고, 추가해보고 수정해보세요.

이미지 1

보시다 시피, 쓸데없는 렌더링을 하고있습니다…

3.2 해결하기

자, 이제 해결해봅시다. 해결법은 매우 간단합니다.

Component Lifecycle API 중 하나인 shouldComponentUpdate() 메소드를 컴포넌트 클래스 안에 작성해주면됩니다.

이 메소드는 컴포넌트를 다시 렌더링 해야 할 지 말지 정의해준답니다.

class ContactInfo extends React.Component {
/* ... */
    shouldComponentUpdate(nextProps, nextState){
    	return (JSON.stringify(nextProps) != JSON.stringify(this.props));	
    } 
/* ... */

이미지 2

자, 이제 필요한 컴포넌트만 렌더링합니다.

이와 같이, 컴포넌트를 Mapping 하고 데이터를 수정 할 떄 코드를 최적화 하는것을 잊지 않도록 합시다.

마무리하며..

어쩌다 보니 강좌가 정..말 길어졌습니다. 그렇게 많은 개념들을 다룬건 아니지만 적용하는 과정이 꽤 길었네요. 이 강좌의 중점인 배열 데이터 변경은 섹션 1 에 모두 있지만, 적용하는 부분을 따라해보시면 React 를 익히는데 도움이 될겁니다.

이 프로젝트에서 사용된 코드는 jsfiddleGitHub에서 리뷰 가능합니다.

다음 강좌에서는 Component Lifecycle API 에 대하여 자세히 알아보겠습니다.

liste

  • hanwool kim

    오타가 있어서 알려드려요
    원소 수정하기에서 필드 간에 ,가 빠져있네요

    field: { $set: “value” }
    field2: { $set: “value2” }

    좋은 강의 해주셔서 감사합니다~

    • 오탈자 지적 감사합니다!

  • hanwool kim

    그리고 마지막에 알려주신 shouldComponentUpdate()를 적용하면 li에 style 추가하는 것이 안되네요.
    이런 문제가 발생하는 이유가 prop 변경 없이 style만 추가하므로, sholudComponentUpdate가 해당 component를 건너뛰는 건가요??

    • 혹시 shouldComponentUpdate 부분에서 값을 return 하지 않았거나, 언제나 false를 반환 하는것이 아닐까요?
      아이템이 선택 될 때, prop isSelected 값이 변경되므로, 건너뛰지는 않을거에요.

      링크 확인해주세요: https://jsfiddle.net/k21ozagp/18/

  • jh

    실무에 적용하다보니 답답한 부분이 생겨서 질문 드려요

    json구조가 아래와 같다면

    {
    user: [
    {
    id:1,
    category: ‘active’,
    name: ‘aaa’,
    is_active: false
    },
    {
    id:2,
    category: ‘active’,
    name: ‘bbb’,
    is_active: false
    },
    {
    id:3,
    category: ‘inactive’,
    name: ‘ccc’,
    is_active: false
    }
    ]
    }

    위 user배열안에서 category가 active인 사용자들의 is_active property를 true로 수정하려면 어떻게 해야 할까요?
    지금 제가 하고 있는 방법은 위 객체를 deep copy를 해서 for문을 돌면서 category 프로퍼티를 체크하면서 is_active수정후

    update문을 사용해서 deep copy한 객체를 $set하면서 수정하고 있는데 너무 비효율적인 작업 같아서 질문 드립니다.

    • is_active를 넣지 말고 그 값을 사용해야하는 부분에서 this.state.user[index] === ‘active’ 를 사용하면 되지 않을까요?

  • 서재민

    궁금한 것이 생겨 문의드립니다.
    강의 마지막부분에 자원낭비줄이기 에서 사용하신 shouldComponentUpdate 메소드를
    ContactInfo 클래스에서 사용하셨는데요

    그러면 저메소드를 render함수가 잇는 모든 클래스에서 사용하면 되는것이 아닌가요?
    컴포넌트의 업데이트가 일어나는 클래스만 생각하여 추가해주어야 하는걸까요?

    • 우선, ContactRemover ContactCreator 에도 render 메소드 내부에 console.log 를 하게된다면,
      데이터가 추가될 때 마다, 컴포넌트에서 render 메소드를 실행하긴 합니다만, 이 때 CPU의 사용률은 사실상 그렇게 크지 않습니다. 어짜피 가상 DOM 에서 처리하기때문에 빠르구요.

      ContactInfo 에서 render() 를 할 때도 그렇게 사용률은 크지 않지만, 여기선 컴포넌트 매핑을 하기 때문에, 컴포넌트의 갯수가 많아 질 수도 있으므로 얘기가 달라지기에, 저렇게 shouldComponentUpdate 를 사용하여 최적화를 한것입니다.

      추가적으로, https://facebook.github.io/react/docs/pure-render-mixin.html 에 대해서 알아보시면 좋을거에요 🙂

      • 서재민

        답변 감사합니다.

        그렇다면.. 나중에 react를 연습을 많이 하여 웹프로젝트를 혼자 해볼생각인데요.
        @velopert:disqus 님(? 뭐라해야할지..)께서 프로젝트를 진행할때 어떠한 용도의 클래스에는 shoudComponentUpdate 메소드를 이용해야하고 어떠한 클래스에선 또다른 메소드를 이용해야하고…
        이러한 팁(?), 노하우(?) 같은 걸 알려주실 수 있을까요… 아니면 나중에 언젠가 따로 글로 정리해주신다면 (+_+// 욕심쟁이) 감사하겠습니다..

        항상 답변 잘달아주셔서 감사합니다.

  • SyaLot

    input에 값이 바뀔때마다 구지 onChange 로 this.setState계속 렌더를 돌릴 필요가 있을까요???
    `this.abc[name] = value`이렇게 변경된 값만 넣어두고 나중에 렌더를 꼭 돌릴일이 있을때만 this.setState({data : this.abc});
    로 하여 최소한에 렌더를 돌리는게 더 좋지 않나요???
    (근데 문서들을 찾아보면 전부 input 같은 경우는 값이 바뀔때마다 this.setState 로 바꿔주고 있고 FaceBook공식문서에도 저렇게 나와있던데 이게더 좋은건가요?? render 관련으로 너무 머리가 혼잡해서 댓글을 달아봅니다)

    • 그런 생각이 드는건 자연스러운것 같습니다. 저 또한 이전에 비슷한 생각을 했구요.

      그 input의 값을 state에 넣는 경우는, 그 값을 “관리” 해야 할 때 넣으시면 되겠습니다

      예를들어, 로그인페이지가 있다고 가정을 해봅시다.

      만약에 submit을 했는데 비밀번호가 틀렸어요, 그러면 기존 비밀번호를 공백으로 설정을 해야하는데, 만약에 말씀하신대로 this의 멤버변수를 공백으로 설정하면 바로 반영되지 않죠. 그렇다고 forceUpdate 하기엔 좀.. 별로이구요. (이런 경우 ref를 사용하면 되긴 하겠네요)

      이런식으로, input값을 직접 수정하는게 아니라 다른 경로로 수정 할 일이 있지 않은 이상 말씀하신대로 배열안에 넣어도 무방하다고 생각합니다.

      저 같은 경우엔 input값을 redux store에 넣어서 사용한답니다. 그러면 다른 컴포넌트에서 그 값에 접근 할 수 있죠.

      컴포넌트를 잘 쪼개면 좀 더 효율적으로 업데이트 할 수 있을겁니다.

      제 경우엔 각 form마다 새 컴포넌트를 만들어줘요.

      • SyaLot

        답글을 지금 확인했습니다. 그러면 하나만 더 물어볼것이 있는데요
        fb문서에서는 this.state.abc = ‘0’ 이렇게 직접접근을 하지 말라고 써있는데 그러면
        this.state.array = [0];
        let array = this.state.array;
        array.push(1);
        이렇게 오브젝트나 배열이 참조되어서 같이 바뀌는 경우는 괜찮을까요? 아니면 깊은 복사를 해서 피해야 되는걸까요?

        • 그렇게 하면 array가 바뀔때 자동으로 업데이트가 되지 않습니다. 그런 경우엔 직접 복사를 하거나 immutability helper를 쓰면 되는거구요.

  • 주홍철

    this.setState({
    list: update(
    this.state.list,
    {
    $push: [newObj, newObj2]
    }
    });
    이부분에서 )하나 빠졌어요!
    this.setState({
    list: update(
    this.state.list,
    {
    $push: [newObj, newObj2]
    }
    )
    });
    이렇게 되야 되요 맨 상단에요 ㅎㅎ
    강의 잘 듣고 있습니다.

  • dali high

    react-addons-update 와 immutable-js 다른 라이브러리같은데 아닌가요? immutable-js를 사용한다고 되어 있는데 react-addons-update가 사용되고 있어서…

    • 이 포스트를 작성 할 시점에 제가 잘못 이해하고 있었던 것 같습니다.

      완전 별개의 라이브러리이며 그저 용도가 조금 비슷합니다.

      이 부분은 조만간 리액트 강좌를 최신화하면서 업데이트 하도록 하겠습니다.

      잘못된 정보를 제공해서 죄송합니다 🙁

  • 강그루 Groo Gang

    와; 이거 따라하다 보니 props, state는 확실히 알게됨

  • Time Spot

    .. 제일끝에 렌더링 회수 최적화시.. state의 isSelected 때문에 한번에 두개가 렌더링됩니다.. 이와같이 한줄 수정하면 되네요.. return (nextProps.username != this.props.username || nextProps.phone != this.props.phone);

  • 이기윤

    궁금한 것이 생겨서 질문드립니다.
    2.2 선택기능 구현하기에서 Contacts 클래스 부분에 isSelected가 호출되는 시점은 언제인지와 어떻게 호출되는지 궁금합니다ㅠㅠ
    에서 onSelect는 child클래스 ContactInfo에서 handleClick()이 실행되면서 onSelect를 호출하는 것 까진 알겠는데 isSelected는 호출하는 부분이 없는데 호출되고 있어서요ㅠㅠ답변 부탁드립니다..!

  • swlee

    좋은 강의 감사합니다. 그런데 궁금한게 있는데, 마지막에 shouldComponentUpdate 소스를 추가했는데, 추가한것만으로는 아무런 변화가 없는거 같은데, 이 메소드를 어디서 사용해야 되는건지 모르겠습니다 ㅠ

    • swlee

      아 해결했습니다. render() 밑에다 추가해야되네요 위쪽에 추가하니까 안됬었네요..아직 어렵네요 ㅠㅠ

  • Weonhee Kim

    안녕하세요, 좋은 강의 너무 감사드려요^^
    리액트 처음 접해서 어렵지만 덕분에 조금씩 이해하고 있어요.
    앞으로 실무에서 쓰게 될 것 같은데 덕분에 개념을 잡고있습니다!

    그런데 마지막 출력물로 수정 기능을 jsfiddle에서 확인해보는데 수정이 되지 않더라고요ㅜㅜ
    본문에는 있던데, 아마 출력물의 Contacts에서 ContactEditor를 쓸 때 ” isSelected={(this.state.selectedKey !=-1)} “가 빠져있어서 수정이 안되는 것 같아요.

    마이너하지만 혹시 저처럼 왜 안되는건지 고민하실 분 있으실까하여 말씀드립니다!

    <ContactEditor onEdit={this._editContact.bind(this)}
    isSelected={(this.state.selectedKey !=-1)} <

    저는 그럼 다음 강의를 들으러 가보겠습니다. 감사합니다. 🙂

  • Donghee Jang

    강의 내용이 항상 좋아서 잘 보고 있습니다
    인쇄해서 종이로 보고 싶은데 글 내용만 인쇄하기가 안되네요 ㅠㅜ

  • Hyun-gyu Kim

    아직까진 props와 state 를 넘나들면서 사용하는게 익숙하지가 않아서 그런데, 어떡하면 좀 더 익숙하게 사용할 수 있을까요….반복은 겁나게 하고있습니다ㅠㅠ

  • We do not recommend doing deep equality checks or using JSON.stringify() in shouldComponentUpdate(). It is very inefficient and will harm performance.

    https://reactjs.org/docs/react-component.html#shouldcomponentupdate

    • 맞습니다 ㅋㅋ 이게 좀 예전에 작성된 글이여서요, 시간나면 이 글을 폐기하고 새로 작성할 계획입니다.

      Immutable.js 같은걸 사용하는것이 좋은 방법이죠.

      • 그래도 글 잘 보고 있습니다 @.@
        감사합니다

  • ninanung

    contactCreater에서 onClick메서드를 관리하는 함수중 onInsert메서드가 나오는데 이건 무슨 기능을 하는거죠? 구글링을 해도 제대로 나오지를 않네요.

    • ninanung

      아, 이건 이해했습니다. 그런데 onInsert함수에 Contacts의 함수를 대입해야 하는데.
      { this._insertContact.bind(this) }가 먹히지 않네요. 함수를 넣어야 할 것에 object를 넣었다는 오류가 나옵니다.

  • 그림자도서관

    안녕하세요 배열의 하위 트리 배열을 다루는 방법을 찾다가 여기까지 왔네요.
    하기 함수와 같이 update와 push를 써서 하위 배열에 새로운 객체를 추가해주었습니다.
    그런데 제가 듣기로 push를 쓰지 말고 항상 concat을 쓰라고 권유받아서
    이 경우에도 $push 대신 $concat을 쓰려고 하니 에러가 납니다.
    어찌된 것인지 설명해주실수 있을지요.

    addLot = (order) => {
    this.setState(
    {
    tickerData: update(this.state.tickerData, {
    [order – 1]: {
    lot_data: {
    $push: [
    {
    turn: this.state.tickerData[order – 1].lot_data.length + 1,
    …this.state.lot_default,
    },
    ],
    },
    },
    })
    }
    );
    };