props
객체가 부모 컴포넌트에서 받아오는 외부 데이터였다면, state
는 각 컴포넌트가 가지고 있는 컴포넌트의 내부 데이터다.
이 state
를 이용하면 element
를 재생성해서 렌더링 하지 않고도 UI
의 변경이 가능한데, 어떤식으로 사용하는지 아래에서 자세히 알아보자!
엘리먼트(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
는 컴포넌트의 내부 데이터다.
각각의 컴포넌트가 개별적인 state
객체를 가지고 있기 때문에, 같은 유형의 컴포넌트라고 해도 서로 다른 state
를 가지게 된다.
그래서 동일한 state
의 변경을 여러 컴포넌트에서 반영하려면 공통된 부모 컴포넌트 중에 가장 가까운 컴포넌트에서 props
를 이용해 자신의 state
를 변경하는 함수를 자식 컴포넌트로 내려주게 되는데, 이것을 state 끌어올리기
라고 한다.
state 끌어올리기
를 사용하지 않은 예제부터 보자.
MyComponent
는 props
를 이용해 Button
컴포넌트를 호출하고 있다.
button element
에 부모 컴포넌트에서 전달된 props.value
가 Up!
일 경우 count
가 1씩 증가하는 핸들러를, 아닐경우 count
가 1씩 감소하는 핸들러를 추가해준다.
렌더링 해서 버튼을 클릭한 결과는 아래와 같다.
Button
이라는 같은 유형의 컴포넌트를 사용해도 state
는 공유되지 않고, 각 컴포넌트가 가진 고유한 state
를 변경시키는 것을 볼 수 있다.
이제 state 끌어올리기
를 이용해서 두 Button
컴포넌트가 같은 state
를 제어하도록 해보자.
먼저 Button
컴포넌트를 아래와 같이 수정하자.
Button.js
부모 컴포넌트의 state
를 제어하기 때문에 생성자 함수와 이벤트 핸들러를 제거하고, props
객체를 통해 함수와 값을 넘겨받는다.
이제 두 Button
컴포넌트의 가장 가까운 부모 컴포넌트인 MyComponent
를 수정하자.
MyComponent
컴포넌트의 state
를 초기화시키고, 이벤트 핸들러의 this
를 MyComponent
에 바인딩 시킨다.
이벤트 핸들러 내부의 this.setState()
함수는 MyComponent
의 state
를 변경시키게 되고, Button
컴포넌트를 호출할 때 props
를 통해 이벤트 핸들러를 자식 컴포넌트에게 전달해준다.
이렇게 되면 Button
컴포넌트는 props
객체를 통해 부모 컴포넌트로부터 전달받은 이벤트 핸들러를 호출함으로써, 서로 다른 컴포넌트가 같은 state
객체를 제어하는 것이 가능해진다.
이제 렌더링 된 결과를 확인해보자.
정상적으로 동작하는 것을 볼 수 있다.
state
객체 사용시 유의점state
객체는 일반적인 JavaScript Object
지만, 다룰 때 알아둬야 할 특징이 몇 가지 존재한다.
아래에서 자세히 알아보자!
state
객체 직접 수정 금지this.state
는 오직 생성자 함수(constructor()
) 안에서만 직접적인 수정이 가능하다.
이유는 간단한 예제를 통해 알아보자.
버튼 changeDriect
는 state
객체를 직접 수정하고, changeSet
은 setState()
메서드를 이용해 state
객체를 변경한다.
렌더링 해서 버튼을 눌러 결과를 확인해보자.
React
컴포넌트 state
의 변경을 감지하면 리렌더링을 수행하는데, state
객체를 직접 수정한 경우에는 렌더링 하지 않는다.
그래서 ChangeDirect
버튼을 눌러도 state
값은 변하지만 UI
업데이트는 진행되지 않은 것이다.
이렇게 렌더링 버그가 발생하는 경우를 방지하기 위해서 꼭 state
객체를 불변 객체인 것처럼 취급하도록 하자.
setState()
메서드를 호출해서 state
를 변경하면 기존의 객체를 덮어쓰는것이 아닌 병합을 진행한다.
역시 간단한 예제를 통해 자세히 알아보자.
초기 state
객체는 name
과 age
프로퍼티를 가지고 있다.
각 input element
의 이벤트 핸들러를 보면 setState()
를 호출할 때 name
과 age
를 모두 수정하는것이 아닌 한 가지 프로퍼티만을 담은 객체를 인자로 해서 호출하고 있다.
이제 렌더링 된 결과를 보도록 하자.
setState()
메서드를 이용해서 state
를 변경할 때 인자로 전달된 객체로 대체되는 것이 아니라, 변경하지 않은 프로퍼티는 기존 값을 유지하는 것을 볼 수 있다.
이렇게 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씩만 증가시키는 것을 볼 수 있다.
React
는 setState()
메서드를 여러번 호출시 효율적인 렌더링을 위해서 일괄 처리(batch processing)를 진행하기 때문인데, 이러한 이유 때문에 setState()
메서드가 비동기로 실행되는 것이다.
위에서 사용한 코드를 의도한 대로 동작하게 하려면 setState()
메서드를 다르게 호출하면 된다.
setState(updater, [callback])
updater
는 state
와 props
를 매개변수로 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