리액트에서 상태 관리란?
UI 프로그래밍에서 상태 관리란?
UI 요소는 다양합니다. 버튼, 드랍다운 메뉴, 체크 상자, 텍스트 출력 요소 등. 이러한 UI 요소는 다양한 상태(State)일 수 있습니다. 예를 들어, 버튼을 클릭한다고 했을 때, 버튼은 다음 상태 변화 과정을 거칩니다.
- 버튼이 눌려진 상태
- 버튼이 눌려진 상태에서 해제되는 상태
- 원래
UI 요소의 상태는 UI 요소의 역할, 즉 기능에 따라 다양한 상태를 갖습니다. UI 요소를 객체(Object)라고 비유하면, 상태는 객체의 멤버 변수라고 할 수 있지요. UI 요소는 대개 복잡한 상태를 갖습니다. 회원가입 양식(form)을 예로 들게요. 이 양식도 UI 요소입니다. Form이라고 하겠습니다. Form엔 사용자ID, E-mail 주소, 비밀번호, 그리고 비밀번호를 확인하는 값을 입력 받으며, 회원가입 버튼이 UI 요소로 포함되어 있습니다.
- 네 개 입력 UI 요소 중 어느 하나라도 입력을 하지 않으면 가입 버튼은 활성화되지 않습니다.
- 모두 입력했더라도 비밀번호와 비밀번호 확인 값이 일치하지 않으면 가입 버튼은 활성화되지 않습니다.
- 사용자ID는 네 글자 이상 입력해야 입력한 것으로 간주합니다.
- E-mail 주소는 유효한 E-mail 형식이어야 입력한 것으로 간주합니다.
- 비밀번호는 여덟 글자 이상 입력해야 입력한 것으로 간주합니다.
- 비밀번호는 사용자ID와 달라야 입력한 것으로 간주합니다.
- 가입 버튼을 누르면 모든 입력 UI 요소가 비활성화 되어 수정할 수 없습니다.
- 서버로부터 가입 요청에 대해 유효하지 않은 값이라는 응답을 받으면 모든 입력 UI 요소는 활성화 상태가 됩니다.
사용자 요구사항에 따라 얼마든지 더 복잡해집니다. 의심이 많은 분이라면 위에 나열된 내용엔 UI 상태(State)와 조건(Condition)이 섞여있다는 걸 눈치채실지도 모르겠군요. 각 UI 요소는 객체로써 다른 객체와 협력하고, 협력에 따라 상태가 달라진다는 걸 설명하려는 의도였습니다. 🙂
회원가입 버튼을 상태 변화하는 과정에 놓고 보죠. 우선 비활성화 상태일 때는 어떤 모양일까요? 대개는 회색 바탕이고 마우스 커서를 올려도 바탕색 변화가 없어서 마치 벽돌처럼 굳은 것처럼 표현하지요. 이 상태에선 버튼을 눌러도 아무 반응이 없고요. 활성화 상태일 땐, 창량한 푸딩캠프의 상징인 파란색이 배경에 깔리고, 마우스 컬러를 올리면 좀 더 연한 파란색으로 바탕이 바뀝니다. 클릭하면 이 버튼이 클릭될 때 수행할 동작이 호출되고요.
이 화면을 매우 낮은 수준으로 내려가 상상해보세요. 운영체제의 제어에 따라 모니터에 버튼을 그립니다. 처음엔 비활성 상태이니 회색 배경으로 회원가입 버튼이 그려집니다. 이제 회원가입 버튼이 활성화되는 조건을 만족해서 활성 상태로 그립니다. 파란 배경이 되지요. 우리 눈에는 버튼의 색상이 교체되는 것으로 보이지만, 실제로는 그 위치에 파란색 버튼으로 새로 그립니다. 그래픽 카드라는 하드웨어 입장에선 버튼이라는 객체는 존재하지 않습니다. 요청받은 좌표에 픽셀 하나 하나 빛을 발광하도록 만들어 그리는 것 뿐입니다. 이 과정이 상상 속에서 그려진다면 이번 편에서 다룰 내용의 핵심을 이해하신 겁니다!
리액트에서 상태 관리
상태 변화에 따른 UI 컴포넌트 처리하는 과정
리액트가 UI 요소인 컴포넌트의 상태를 다루는 방식도 앞선 설명과 크게 다르지 않습니다. 왜냐하면 앞선 설명은 실은 리액트의 상태를 설명하려고 밑그림처럼 설명을 쓴 것이거든요. 😅 용어부터 살펴볼까요? 리액트는 컴포넌트의 상태가 변하면 렌더링(rendering)합니다. 하지만 아직 화면에 그리진 않습니다. 회원가입 버튼이 활성 상태가 되어서 렌더링은 하지만, 하지만 파란 배경으로 그린 건 아닙니다. 그리고 잠시 후, 잠시라고 하기엔 우리가 거의 인지하지 못할 정도로 짧은 시간이 지난 후에 비로소 바뀐 상태에 맞춰 그리는데, 이를 페인팅(painting)이라 합니다.
페인팅은 리액트가 웹 브라우저에게 요청해서 이뤄지며, 꽤 느렸습니다. 느렸다고 표현하는 이유는, 최신 브라우저는 최적화가 잘 되어 있어서 과거보다 더 빠르게 페인팅, 브라우저 입장(?)에선 DOM 처리를 하기 때문입니다. 어쨌든 리액트가 개발되고 한참 동안은 DOM을 변형하는 건 꽤 느린 작업이었습니다. 그래서 리액트는 렌더링이나 리-렌더링(re-rendering) 처리를 할 때마다 페인팅을 하진 않고, 렌더링 정보를 모았다가 일괄 처리(batch)합니다. 이 과정은 비동기(Asynchronous)로 처리되지요. 이에 대해서는 아래에서 따로 살펴 보겠습니다.
그럼 리액트는 언제 어떻게 렌더링을 할지 판단하는 걸까요? 리액트는 컴포넌트를 렌더링하면(렌더링이 화면에 그리는 동작이 아니라는 걸 유의하세요!), 렌더링된 결과물을 사진 찍듯이 보관하는데, 이 단위를 스냅샷(Snapshot)이라고 합니다. JavaScript에서 동작하니 JavaScript 객체입니다. 자, 이제 회원가입 버튼이 활성 상태로 변경되었습니다. 이 변경을 재조정(reconciliation)이라고 합니다. 재조정이 일어나면 비동기로 변경된 정보가 리액트의 대기열(queue)에 쌓여 들어가고, 일괄 처리(batch) 때가 되면 리액트는 스냅샷 찍어 놓은 컴포넌트 객체(React-dom)와 상태가 조정된 객체를 비교합니다. 만약 상태가 3개가 바뀌었으면 이들을 한꺼번에 반영하며, 최종적으로 DOM 을 조작하여 웹 브라우저가 해당 UI 요소(회원가입 버튼)를 새로 그립니다(페인팅).
어려우신가요? 거의 다 왔어요. 힘냅시다!
useState() 함수로 상태 관리하기
리액트 Hooks이란? 편에서 리액트 훅으로 리액트의 컴포넌트의 상태를 관리한다고 설명하였습니다. 잠시 우리가 작성한 코드를 꺼내보겠습니다. 오랜만이군요!
코드 중에서 useState() 함수가 상태 값을 다루는 리액트 훅입니다. 이 함수를 실행하면 두 개 객체를 배열에 담아 반환하는데, 첫 번째 객체는 상태의 값을 담고 있습니다. 두 번째는 상태의 값을 변경하는 함수입니다. setEmail() 함수로 상태의 값을 변경하면, email의 값이 바뀌지요. 리액트는 setEmail()가 호출되는 것으로 상태 변화를 감지합니다. email의 값을 바꾸는 것으로는 반응하지 않지요.
그런데 신기하다고 느끼지 않으시나요? Subscription 컴포넌트는 리액트 입장에서나 컴포넌트이지, 이 코드가 구동되는 환경은 JavaScript라는 언어이고, JavaScript 입장에서 Subscription은 함수일 뿐입니다. 함수는 실행하면 인자를 전달받으면서 시작되고, return문을 만나 값을 함수 호출자에게 반환하면서 실행이 끝납니다. 매번 이렇게 동작합니다. 그런데 useState() 함수를 쓰면 마치 함수의 특정 위치에서 시작하는 것처럼, 좀 더 정확하게는 함수 내부의 값이 보존되는 것처럼 동작하는 것이거든요. 아까 표현한 것처럼 스냅샷을 뜬 것처럼 동작하는 것인데, 어떻게 함수가 이렇게 동작하는 걸까요?
바로 이 점이 useState() 훅, 더 넓게는 리액트 훅의 역할이자 의의입니다. 바로 선언형 프로그래밍(declarative programming)이지요. 어떤 마법을 부리는지는 사용자가 알 필요 없고, 우리가 필요한 위치에 useState()로 상태를 선언해놓으면 그만이죠. 이에 대해서는 Props로 자식 Element 전달하기 편에서도 설명한 바 있습니다.
비동기로(Asynchronous) 처리되는 상태 변화
앞서 제가 리액트는 상태 처리를 비동기로 처리한다고 말씀드렸는데요. 실습으로 확인해보겠습니다. Subscription 컴포넌트를 조금 고치겠습니다.
어디가 바뀌었냐면, onChange 쪽입니다. 자, 우선 hannal@puddingcamp.com을 입력해보세요. 웹 브라우저에서 개발자 도구는 일단 무시하시고요. 이제 콘솔을 청소(clear)하시고요. 백 스페이스를 한 번 누르세요. 그럼 hannal@puddingcamp.com이 아니라 hannal@puddingcamp.co이 됩니다. 그럼 email에도 이 내용이 반영됐을테니 console.log(email)코드로 인해 웹 브라우저 콘솔에는 hannal@puddingcamp.co가 찍힐 겁니다. 해보시면 아시겠지만, 실제로는 hannal@puddingcamp.com이라는 변경 전 값이 찍힙니다. 리액트가 상태 변화 처리를 비동기로 하는 사이에 console.log() 코드가 실행되어서 그렇습니다.
이 동작은 여러분이 숙지해야 합니다. 대부분 사람에겐 비동기보다 동기식 동작을 머릿 속에 그리는 게 쉽고 익숙하고 친숙합니다. 그래서 리액트가 상태 처리를 동기식으로 한다고 머리에 심성 모형이 그려져 있다면 버그를 만들 가능성이 커집니다. 간단히 말해 실수하기 일쑤입니다. 특히 상태가 한 컴포넌트 안에 복잡하게 서로 얽히면 더욱 그럴 실수할 가능성이 크지요.
마치기 전에, 위 코드에서 어떻게 입력하는대로 콘솔을 찍을 수 있는지 알려 드릴까요? 아주 간단합니다.
허무할 정도로 시시하죠? 😅 newValue과 email은 엄밀히 말해 좀 다른, 뭐랄까, 거주지가 다르달까요. email은 리액트의 상태 관리 체계에 있는 객체이고, newValue는 내용물(값)이 같다는 걸 이용해서 출력에 활용한 것뿐이지요.
푸딩캠프 이야기
새로운 커피챗 코치님 합류
그동안 한날이 여러 주제로 커피챗을 운영했는데요. 권준호(콴)님이 커피챗 코치로 합류하셨습니다! 콴 코치님께서는 주로 Product Manager/Product Owner 직군을 대상으로 커피챗 자리에서 코칭이나 멘토링을 하실 예정이에요. 다음은 콴 코치님의 인사말과 소개입니다.
안녕하세요. PMF 파트너스 콴입니다.
제품을 개발하는 프로덕트 매니저로, 또 그렇게 제품을 개발하는 스타트업에 투자하는 초기투자자로서 많은 기업과 제품을 직간접으로 만나오면서, 스타트업은 제품을 산출하는 것을 목표로 하는 조직이라는 믿음을 가지고 있습니다.
하지만 그 조직이 제품을 산출하는 과정은 조직적이지 않고 체계적이지 않습니다. 극초기 기업의 낭만시기를 지나오면 새로운 사람들과, 또 손바뀜이 일어나는 담당자들과 지속적으로 제품을 개발하고 산출해낼 수 있는 Process가 필요하지만 대부분의 경우 해왔던 그대로를 반복하고 있을 뿐이어서 제품이 원하는 시점에 원했던 형상으로 산출되지 않음으로서 벌어지는 문제를 목도하곤 합니다.
구성원들이 실망하고 이탈하거나, 투자금이 너무 과도하게 소진되거나, 시장에서 제대로 도전해보지 못하고 기회를 잃어버리는 일들입니다.
제품을 형상화하고 그것을 형상대로 산출하는 과정을 조직에 익숙하게 체화시키면 그것에 곧 제품문화가 됩니다. PM으로서 개발자들에게, 또 경영진에게 제품의 형상을 정확하게 전달하고 의도했던 바로 그 제품이 정확하게 체계적으로 산출하는 과정을 전달합니다. 이런 과정을 통해서 성장하는 PM이 될 수 있을 것입니다.
경력 및 이력
- 현) PMF파트너스 대표 / PMF개인투자조합 대표파트너, 원티드/요즘IT 기고, 대기업 제품부서 강의 및 세션 등
- 전) 야놀자 R&D그룹 PMO head
- 전) 뱅크샐러드 마이데이터 그룹 그룹장 / PO chapter lead
- 전) 쉬프트 액셀러레이터 부대표
- 전) 삼성전자 무선사업부 삼성페이 PM
- 전) 세컨커머셜 공동창업, 사업/제품 총괄
강의 / 멘토링
- LG U+ 디지털커머스랩 제품형상화 강의 및 워크숍
- 푸딩캠프 학습과 성장 컨퍼런스 2024
- 원티드 하이파이브 2024 세션 (AI시대에 PM은 살아남을 수 있을까)
- 요즘IT / 원티드 기고 등
- 스타트업 심사평가 및 육성 다수
한날 코치의 커피챗이 평균 3주 대기를 하는 걸 봤을 때, 아직 알려지지 않은 이때 재빨리 커피챗을 신청하세요!
의견을 남겨주세요