All Articles

setState를 함수형으로 사용하기

리액트를 사용할 때 상태관리를 어떻게 해야할까? 그런데 상태 관리를 고민하기 이전에 무엇이 상태(state)고 어떤 것이 프로퍼티(props)가 되어야하는 것일까? 그리고 setState 어떻게 동작하는 것일까?

궁금증과 부족함을 하나씩 채워가는 리액트 관련 포스팅.

이번엔 setState가 어떤식으로 작동하는지, 현재 사용하는 방식 보다 좋은 방식은 무엇일까 고민하며 찾아본 것을 정리해보자.


프로퍼티와 상태(state)의 개념

컴포넌트 트리에서 사용하는 모든 속성은 상태(state)와 프로퍼티(props)로 나뉜다. 상호작용이 필요한 경우에는 상태에 보관되어야 하고, 그 외 나머지는 모두 프로퍼티 형식으로 전달한다.

상태를 업데이트하는 방법

리액트에서 상태를 업데이트 하려면 setState() 메소드를 호출한다. setState는 업데이트 할 부분을 포함하는 객체를 인자로 받는다. 그리고 인자로 받아온 객체를 state에 merge하는 방식으로 상태를 업데이트 한다.

좀 더 자세히 말하자면 merge 된 객체로 reconciliation을 시작하고 새로운 트리를 그려서 이전 트리와 비교해서 변경된 부분을 파악하고 DOM을 업데이트 하는 것이다.

하지만 사는게 그렇듯 뭐든 처음 내가 생각한대로 내 뜻 대로는 되지 않는 법. 아래의 코드를 살펴보자.

img

img

왜 4야 6이 아니고

이전에 setState() 는 await와 사용이 가능할까? 포스팅에서 잠깐 언급하고 넘어갔던 것 처럼, setState는 비동기적으로 작동한다. 그래서 위 코드처럼 원하는 상태를 얻을 수 가 없다.

setState는 어떤식으로 동작하길래 그런걸까?

setState의 상태 업데이트 방식

setState가 위 방식으로 작동하지 않는 이유는 무엇일까?

리액트가 여러번 setState를 만나게 되면 batching하여 작업을 한다. 매번 호출 순서대로 바로 업데이트하지 않고 인자로 전달된 객체들을 하나로 합친 뒤에 업데이트하기 때문이다.

객체를 하나로 합친다고? 그게 무슨 말인가.

setState가 여러번 호출되면 인자로 전달된 각 객체를 추출하여 단일 객체로 merge 한다. 그렇게 만들어진 단일 객체를 사용해서 상태를 업데이트 하는 것이다.

이런 과정을 오브젝트 컴포지션이라고 하는데 코드로 풀어보면 아래와 같다.

img

img

이것이 첫번째 예제에서 this.state.value가 6이 아니라 4인 이유이다.

객체가 동일한 키를 가지고 있다면 가장 마지막에 전달된 객체의 키값이 덮어쓰여진다. 간단히 코드로 풀어보면 아래와 같다.

img

img

카스가 아니라 맥스가 된 이유

이처럼 setState는 상태를 비동기적을 갱신한다. 그래서 가끔 문제가 없어 보이는 코드인데 상태가 원하는대로 업데이트 되지 않는 일이 벌어질 때도 있다. 그렇다면 상태를 업데이트 할 때 좀 더 믿을만한 이전 상태를 받아오는 방법은 없을까?

함수를 받는 setState

setState는 객체뿐 아니라 함수도 인자로 받을 수 있다. 아래 코드로 살펴보자.

img

img

함수를 인자로 전달받은 setState는 원하는 시점의 상태를 가지고 업데이트를 한다.

객체가 아닌 함수형 setState가 호출되면 merge 할 객체가 없기 때문에 호출된 순서대로 함수를 큐에 넣는다. 그 후에 큐의 각 함수를 호출하여 함수형 setState의 이전 상태를 전달하여 상태를 업데이트 하는 것이다(이제 내 뜻대로 되는건가!!).

이 말은 상태를 업데이트 하는 함수를 외부에 선언해 놓고 가져다 쓰기만 해도 된다는 소리다. 처음에 이런 방법이 있다는 걸 알았을 때 왜 이런 신박한 방법을 몰랐을까 즐거워했(?)다.

img

img

이런식으로 상태를 바꿔주는 함수는 그저 함수일 뿐이다. setState는 가져다 쓰기만 하면 된다. 상태변경 로직을 외부 모듈에 모아두고 필요한 컴포넌트에서 import 해서 쓸 수 도 있다. 이제 컴포넌트 안에 모든 업데이트 로직이 들어있을 필요가 없게 된 것이다!

더욱 좋은 점은 테스트도 쉬워진다는 것이다.

img

img


뭐든지 생각없이 습관적으로 사용하는 것은 좋지 않은 것 같다. setState도 마찬가지로 습관처럼 필요한 객체만 넘기거나, 종종 함수를 인자로 사용할 때도 정확히 어떤식으로 작동하는지 이해하기 보다는 결과를 만들기에 급급했던 것 같다.

안을 들여다보면 내가 당연하다고 생각하는 것들은, 내가 당연하게 생각하는 것과는 다른 방식으로 돌아갈 때가 많다. 주의깊게 살펴보는 습관을 갖자.

별거 아닌 것 같은데도 왜 이걸 그때 몰랐을까 짜릿해하며 하나씩 배워가는 과정이 종종 힘들때도 있지만 즐겁다.

나와 비슷한 사람들, 리액트 상태변화는 그냥 setState에 대충 넣으면 되는거 아잉가여 하는 분들에게도 조금이나마 도움이 되었으면 좋겠다. 더 신박한 방법이 있다면 그걸 알려주시면 너무 좋습니다.

잘못된 부분이나 추가할 부분들에 대한 의견은 언제나 환영입니다.

참고자료

Functional setState is the future of ReactUpdate: I gave a follow up talk on this topic at React Rally. While this post is more about the “functional setState”…medium.freecodecamp.org

React에서 Redux 전에 배워야 할 8가지이 글은 Robin Wieruch의 8 things to learn in React before using Redux 번역입니다. React에서 Redux 전에 배워야 할 8가지 상태 관리(State…edykim.com

RFClarification: why is setState asynchronous? · Issue #11527 · facebook/reactFor quite a while I’ve tried to understood why setState is asynchronous. And failing to find an answer to it in the…github.com