상태(State)

JaeungE·2022년 1월 14일
0

React 찍어먹기

목록 보기
3/8
post-thumbnail

props 객체가 부모 컴포넌트에서 받아오는 외부 데이터였다면, state는 각 컴포넌트가 가지고 있는 컴포넌트의 내부 데이터다.

state를 이용하면 element를 재생성해서 렌더링 하지 않고도 UI의 변경이 가능한데, 어떤식으로 사용하는지 아래에서 자세히 알아보자!



State

엘리먼트(Element) 에서 element불변객체이기 때문에 element의 데이터를 수정하고 싶다면 재생성 해야한다고 했었지만, 컴포넌트의 state 를 이용하면 element를 다시 만들지 않아도 데이터의 변경이 가능하다.

버튼을 클릭하면 count를 증가시키는 간단한 예제를 보면서 이해해보자.


class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = { count : 0 };
    }
  
    render() {
        return (
            <div>
                <h1>Count : {this.state.count} </h1>
                <button>click me!</button>
            </div>
        );
    }
}

state를 사용하려면 먼저 초기화 작업이 필요하다. 그래서 constructor() 함수를 구현해서 state 프로퍼티를 초기화 시켜준다.

state도 일반적인 JavaScript Object이기 때문에 객체 리터럴을 이용해서 초기화 해주는 것을 볼 수 있다.

주의할 점은 this.state 를 이용한 초기화는 생성자 함수에서만 가능하다는 것을 알아두자.

이제 버튼에 클릭하면 state.count를 1씩 증가시키는 이벤트 핸들러를 추가해주자.


class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = { count : 0 };
        this.addCount = this.addCount.bind(this);
    }

    addCount() {
        this.setState({ count : ++this.state.count });
    }

    render() {
        return (
            <div>
                <h1>Count : {this.state.count} </h1>
                <button onClick={this.addCount}>click me!</button>
            </div>
        );
    }
}

이벤트 핸들러 addCount() 함수를 만들었다.

함수의 내용을 보면 this.setState() 함수를 이용해서 state 객체를 수정하는것을 볼 수 있는데, 이처럼 state 객체는 항상 setState() 메서드를 이용해서 조작해야 한다.

setState()의 문법은 아래와 같다.

setState(stateChange[, callback])

stateChange는 객체 형태이며, 뒤의 callback 함수는 setState()에 의한 렌더링이 완료된 후 실행될 callback 함수이다.

하지만 callback 함수를 등록하는 대신 나중에 포스팅할 생명주기 메서드를 사용하는 것을 권장한다.

그리고 생성자 함수를 보면 this.addCount 프로퍼티에 this 바인딩을 하는것을 볼 수 있는데, 이해가 되지 않는다면 이전에 작성한 this 그리고 apply, call, bind 게시물을 참고하자!

이벤트 핸들러도 만들어서 추가했으니 전체 코드를 한 번 훑어보자.



이제 렌더링 된 결과를 보면서 의도한 대로 작동하는지 확인하자.



state.count의 변경에 따라 새롭게 렌더링 되는것을 볼 수 있다.




State 끌어올리기(Lifting state up)

위에서도 보았듯이 state는 컴포넌트의 내부 데이터다.

각각의 컴포넌트가 개별적인 state 객체를 가지고 있기 때문에, 같은 유형의 컴포넌트라고 해도 서로 다른 state를 가지게 된다.

그래서 동일한 state의 변경을 여러 컴포넌트에서 반영하려면 공통된 부모 컴포넌트 중에 가장 가까운 컴포넌트에서 props 를 이용해 자신의 state를 변경하는 함수를 자식 컴포넌트로 내려주게 되는데, 이것을 state 끌어올리기라고 한다.

state 끌어올리기를 사용하지 않은 예제부터 보자.



MyComponentprops를 이용해 Button 컴포넌트를 호출하고 있다.



button element에 부모 컴포넌트에서 전달된 props.valueUp!일 경우 count가 1씩 증가하는 핸들러를, 아닐경우 count가 1씩 감소하는 핸들러를 추가해준다.

렌더링 해서 버튼을 클릭한 결과는 아래와 같다.



Button 이라는 같은 유형의 컴포넌트를 사용해도 state는 공유되지 않고, 각 컴포넌트가 가진 고유한 state를 변경시키는 것을 볼 수 있다.

이제 state 끌어올리기를 이용해서 두 Button 컴포넌트가 같은 state를 제어하도록 해보자.





먼저 Button 컴포넌트를 아래와 같이 수정하자.


Button.js


부모 컴포넌트의 state를 제어하기 때문에 생성자 함수와 이벤트 핸들러를 제거하고, props 객체를 통해 함수와 값을 넘겨받는다.


이제 두 Button 컴포넌트의 가장 가까운 부모 컴포넌트인 MyComponent를 수정하자.



MyComponent 컴포넌트의 state를 초기화시키고, 이벤트 핸들러의 thisMyComponent에 바인딩 시킨다.

이벤트 핸들러 내부의 this.setState() 함수는 MyComponentstate를 변경시키게 되고, Button 컴포넌트를 호출할 때 props를 통해 이벤트 핸들러를 자식 컴포넌트에게 전달해준다.

이렇게 되면 Button 컴포넌트는 props 객체를 통해 부모 컴포넌트로부터 전달받은 이벤트 핸들러를 호출함으로써, 서로 다른 컴포넌트가 같은 state 객체를 제어하는 것이 가능해진다.

이제 렌더링 된 결과를 확인해보자.



정상적으로 동작하는 것을 볼 수 있다.




state 객체 사용시 유의점

state 객체는 일반적인 JavaScript Object지만, 다룰 때 알아둬야 할 특징이 몇 가지 존재한다.

아래에서 자세히 알아보자!


state 객체 직접 수정 금지

this.state는 오직 생성자 함수(constructor()) 안에서만 직접적인 수정이 가능하다.

이유는 간단한 예제를 통해 알아보자.



버튼 changeDriectstate 객체를 직접 수정하고, changeSetsetState() 메서드를 이용해 state 객체를 변경한다.

렌더링 해서 버튼을 눌러 결과를 확인해보자.



React 컴포넌트 state의 변경을 감지하면 리렌더링을 수행하는데, state 객체를 직접 수정한 경우에는 렌더링 하지 않는다.

그래서 ChangeDirect 버튼을 눌러도 state 값은 변하지만 UI 업데이트는 진행되지 않은 것이다.

이렇게 렌더링 버그가 발생하는 경우를 방지하기 위해서 꼭 state 객체를 불변 객체인 것처럼 취급하도록 하자.



state 병합

setState() 메서드를 호출해서 state를 변경하면 기존의 객체를 덮어쓰는것이 아닌 병합을 진행한다.

역시 간단한 예제를 통해 자세히 알아보자.



초기 state 객체는 nameage 프로퍼티를 가지고 있다.

input element의 이벤트 핸들러를 보면 setState()를 호출할 때 nameage를 모두 수정하는것이 아닌 한 가지 프로퍼티만을 담은 객체를 인자로 해서 호출하고 있다.

이제 렌더링 된 결과를 보도록 하자.



setState() 메서드를 이용해서 state를 변경할 때 인자로 전달된 객체로 대체되는 것이 아니라, 변경하지 않은 프로퍼티는 기존 값을 유지하는 것을 볼 수 있다.

이렇게 setState() 메서드는 병합을 통해 각 프로퍼티를 개별적으로 업데이트할 수 있다.



setState()는 비동기 함수다

컴포넌트의 setState() 메서드는 state 객체를 업데이트 할 때 동기식 흐름을 보장하지 않는다.

그래서 setState()를 호출했다고 해서 this.state 가 곧바로 업데이트가 되는것을 기대하기는 어렵다.

정말로 비동기식으로 작동하는지 예제를 보면서 알아보자.



increaseCount(number) 함수는 호출 인자로 넘겨받은 정수만큼 this.setState() 메서드를 이용해 현재 this.state.count에 1씩 더해주는 작업을 반복하는 함수다.

그리고 button element 에 이벤트 핸들러를 등록할 때 다시 bind 메서드를 이용해 bound 함수를 넘겨주는데, 이해가 되지 않는다면 이벤트 처리하기 - React를 참고하자.

여기서 의도한 것은 Count + 1 버튼은 count를 1 증가시키고, Count + 3 버튼은 count를 3 증가시키는 것이다.

정상적으로 동작하는지 아래에서 확인해보자!



원했던 동작과는 다르게 두 버튼 모두 count를 1씩만 증가시키는 것을 볼 수 있다.

ReactsetState() 메서드를 여러번 호출시 효율적인 렌더링을 위해서 일괄 처리(batch processing)를 진행하기 때문인데, 이러한 이유 때문에 setState() 메서드가 비동기로 실행되는 것이다.

위에서 사용한 코드를 의도한 대로 동작하게 하려면 setState() 메서드를 다르게 호출하면 된다.

setState(updater, [callback])

updaterstateprops를 매개변수로 stateChange 객체를 반환하는 함수이다.

(state, props) => stateChange

또한, updater의 매개변수로 사용되는 객체들은 모두 최신값을 보장한다.


그럼 이제 stateChange 대신 updater를 이용해서 코드를 고쳐보자.



화살표 함수(Arrow Function)가 아닌 일반 함수로 작성해도 같다.

이때 주의할 점은 updater 사용 시 this.state를 참조하는 것이 아닌, callback 함수의 매개변수인 state를 사용한다는 점에 주의하도록 하자.

이제 정상적으로 작동하는지 확인해보자.



의도했던 대로 실행되는 것을 볼 수 있다.





참고 자료

State and Lifecycle - React
https://ko.reactjs.org/docs/state-and-lifecycle.html


React.Component - React
https://ko.reactjs.org/docs/react-component.html


컴포넌트 State - React
https://ko.reactjs.org/docs/faq-state.html


State 끌어올리기 - React
https://ko.reactjs.org/docs/lifting-state-up.html

0개의 댓글