리액트는 UI를 조작하는 선언적인 방법을 제공해요. 개별적인 UI 조각들을 직접 조작하는 대신에 컴포넌트가 가질 있는 다양한 상태를 설명하고 이를 사용자 입력에 따라 바꿀 수 있어요. 이는 UI에 대해 디자이너가 생각하는 방식과 유사해요.
이 페이지에서는
- 선언형 UI 프로그래밍이 명령형 UI 프로그래밍과 어떻게 다른지
- 컴포넌트가 가지고 있을 여러 시각적 상태를 어떻게 하나씩 세는지
- 코드에 있는 다른 시각적 상태 사이에서 변화를 어떻게 발생시키는지
를 알아볼 거예요.
How declarative UI compares to imperative | 선언형 UI와 명령형 UI의 차이
UI 상호 작용을 디자인할 때, 여러분은 아마도 UI가 사용자 액션에 반응하여 어떻게 변하는지에 대해 생각할 거예요. 사용자가 정답을 제출하는 폼에 대해 생각해볼게요.
- 폼에 무언가를 입력하면 "Submit" 버튼이 나타나야 해요.
- "Submit" 버튼을 누르면 폼과 버튼은 모두 비활성화 상태가 되어야하고 스피너가 보여야해요.
- 만약 네트워크 요청에 성공하면 폼은 숨겨져야하고 "Thank you" 메시지가 나타나야해요.
- 만약 네트워크 요청에 실패한다면 오류 메시지가 보이고 폼은 다시 활성화된 상태가 돼요.
명령형 프로그래밍에서는 위의 내용은 상호작용을 구현하는 방법에 해당해요. 무슨 일이 방금 일어났는지에 의해 UI를 조작하기 위해서 정확한 명령을 작성해야해요. 이번엔 다른 예시를 볼게요. 누군가의 옆에서 차를 타고 있고 그들에게 어디로 가야하는지 길을 하나하나 알려줘야한다고 생각해보세요.

Illustrated by Rachel Lee Nabors (출처: react.dev)
그들은 어디로 가는지 모르기 떄문에 오직 여러분의 명령만을 따라요. (그리고 만약 명령이 잘못되었다면 결국에 잘못된 장소에 도착할 거예요.) 이러한 방식을 명령적이다라고 하는데, 스피너부터 버튼까지 이르는 각 요소에 컴퓨터에서 UI를 어떻게 업데이트 할지를 말해주는 "명령을 내리기" 때문이에요.
이 명령형 UI 프로그래밍의 예시서 폼은 리액트 없이 구현되었어요. 오직 브라우저 DOM만을 사용해요.
UI를 명령적으로 조작하면 각각 예시에서는 충분히 잘 동작하지만 더욱 복잡한 시스템에서 관리해야 할수록 기하급수적으로 어려워질 거예요. 새로운 UI 요소 또는 새로운 상호작용을 추가하는 것은 버그를 유발하지 않는지 확인하기 위해 모든 기존 코드를 하나씩 신중하게 살필 필요가 있어요. (예를 들면 무언가를 보여주거나 숨기는 것을 까먹지 않았는지를요.)
리액트는 이 문제점을 해결하기 위해 구현되었어요.
리액트에서는 직접적으로 UI를 조작하지 않아요. 즉, 여러분들이 직접 컴포넌트를 활성화시키거나, 비활성화 시키거나, 보여주거나, 숨길 필요지 않아요. 대신 여러분은 무엇을 보여주고 싶은지를 선언하고 리액트는 어떻게 UI를 업데이트 해야하는지를 알아내요. 텍시에 타서 기사님에게 어디서 꺾어야하는지가 아니라 어디로 가고싶은지를 이야기한다고 생각하면 돼요. 원하는 위치에 데려다 주는 것은 기사님의 일이고 심지어는 여러분들이 모르는 지름길을 알고 있을지도 몰라요.

Illustrated by Rachel Lee Nabors (출처: react.dev)
Thinking about UI declaratively | UI를 선언적으로 생각하기
어떻게 폼을 명령적으로 구현하는지는 위에서 보았을 거예요. 리액트의 관점에서는 어떻게 해야하는지를 보다 잘 이해하기 위해서는 이 UI를 아래의 과정을 통해 리액트에서 다시 하나씩 구현해 나갈 거예요.
- 컴포넌트의 다른 시각적 상태를 식별하세요.
- 이러한 상태 변화가 무엇을 발생시킬지 결정하세요.
useState
를 사용하여 메모리 안의 상태를 표현하세요.- 불필요한 상태 변수를 제거하세요.
- 상태를 결정하는 이벤트 핸들러를 연결하세요.
Step 1: Identify your component’s different visual states | 1단계: 컴포넌트의 다른 시각적 상태를 식별하기
컴퓨터 과학에서 "상태 기계"가 여러 "상태" 중 하나라는 것을 에 대해 들어본 적이 있을 거예요. 만약 디자이너와 함께 일한다면 다른 "시각적 상태"에 대한 목업을 보게 될 거예요. 리액트는 디자인과 컴퓨터 과학의 교집합을 상징하기 때문에 이 두 아이디어들은 모두 영감의 원천이 돼요.
먼저 여러분은 사용자가 보게될 UI의 모든 "상태"들을 시각화할 필요가 있어요.
- 비어있는 상태 : 폼은 비활성화된 "Submit" 버튼을 가져요.
- 입력 중인 상태 : 폼은 활성화된 "Submit" 버튼을 가져요.
- 제출 하는 상태 : 폼은 완벽히 비활성화 돼요. 스피너가 보여져요.
- 성공한 상태 : "Thank you" 메시지가 폼 대신 보여져요.
- 에러가 생긴 상태 : 입력 중인 상태와 동일하지만 추가적으로 에러 메시지가 떠요.
디자이너와 같이 여러분은 로직을 추가하기 전에 다른 상태에 대한 "모의"를 하거나 "모형"을 만들고 싶을 거예요. 예를 들어, 여기 폼의 시각적 부분만을 가진 모형이 있어요. 이 모형은 'empty'
를 기본값으로 가지는 status
라는 prop에 의해서 조정돼요.
원하는 것으로 prop을 호출할 수 있고 이름을 짓는 것은 결코 중요하지 않아요. status = 'empty'
를 status = 'success'
로 수정해서 성공 메시지가 나오는지 보세요. 모형은 여러분들이 로직을 연결하기 전에 빠르게 UI를 반복하도록 해줘요. 다음 예시는 여전히 status
prop에 의해 "조정되는" 같은 컴포넌트지만 조금 더 구체화된 프로토타입이에요.
많은 시각적 상태들을 한 번에 보여주기더보기만약 컴포넌트가 많은 시각적 상태를 갖고 있다면 한 페이지에서 전부 보여주는 것이 편리해요.
이와 같은 페이지는 "살아있는 스타일 가이드" 또는 "스토리북"이라고 불려요.
Step 2: Determine what triggers those state changes | 2단계: 이러한 상태 변화가 무엇을 발생시킬지 결정하기
여러분은 두 가지 입력에 반응하여 상태를 업데이트 할 수 있어요.
- 버튼을 누르거나, 필드에 타이핑을 치거나, 링크로 이동하는 등의 사람의 입력
- 네트워크 요청이 도착하거나, 타임아웃이 끝났거나, 이미지가 로딩된 것과 같은 컴퓨터의 입력


Illustrated by Rachel Lee Nabors (출처: react.dev)
두 가지 경우 무두에서 여러분은 UI를 업데이트하기 위한 상태 변수를 설정해야해요. 개발하고 있는 폼에서는 몇 개의 다른 입력에 반응하여 상태를 바꿔야해요.
- 텍스트 입력을 바꾸는 것(사람)은 상태를 텍스트 박스가 비어있는지 아닌지에 의해 비어 있는 상태*를 *타이핑 하고 있는 상태 또는 그 반대로 바꿔야해요.
- Submit 버튼을 누르는 것(사람)은 제출 중인 상태로 변경해야해요.
- 성공적인 네트워크 요청(컴퓨터)는 성공 상태로 변경해야해요.
- 실패한 네트워크 요청(컴퓨터)는 알맞은 에러 메시지를 가지는 에러 상태로 변경해야해요.
노트
사람의 입력은 종종 이벤트 핸들러가 필요해요!
이 흐름을 시각적으로 표현하려면 종이에 각 상태를 라벨이 있는 원으로 그리고 상태 간의 변화를 화살표로 그리세요. 이 방법으로 많은 흐름들을 그려보고 구현하기 훨씬 전에 버그를 가려낼 수 있어요.
폼의 상태들
Illustrated by Rachel Lee Nabors (출처: react.dev)
Step 3: Represent the state in memory with useState
| 3단계: useState
를 사용하여 메모리 안에서 상태 표현하기
그 다음으로는 useState
을 사용하여 메모리 안에서 컴포넌트의 시각적 상태들을 표현해야해요. 간결함이 포인트에요. 각각의 상태 조각은 "움직이는 조각"이고 여러분은 가능한 적은 "움직이는 조각"을 만들어야해요. 복잡하면 복잡할 수록 더 많은 버그가 발생해요!
무조건 그곳에 있어야만하는 상태로 시작해보세요. 예를 들어서 입력창에 대한 상태로 answer
을 저장해야하고 (만약 있다면) 마지막 에러를 저장하기 위한 error
가 있어야해요.
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
그리고나서 보여주고 싶은 시각적 상태 중 하나를 보여주는 상태 변수가 필요해요. 보통은 메모리에 표현하는 방법은 여러개이기 때문에 실험을 해야해요.
만약 가장 좋은 방법을 즉시 생각하기가 너무 어렵다면 여러분이 가능한 모든 시각적 상태가 포함된다고 단언할 수 있을 만큼 충분한 상태를 추가하는 것부터 시작하세요.
const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
여러분의 첫 번째 아이디어는 최고는 아니겠지만 괜찮아요. 상태를 리팩토링하는 것도 과정에 있어요!
Step 4: Remove any non-essential state variables | 4단계: 불필요한 상태 변수 제거하기
상태에 있는 중복을 피하고 싶기 때문에 무엇이 필요한지를 추적해야해요. 상태 구조를 리팩토링하는데 약간의 시간을 사용하면 컴포넌트를 이해하기 쉽게 만들고, 중복을 줄이고, 의도하지 않은 의미들을 피할 수 있어요. 목표는 메모리 안에 있는 상태가 사용자가 보게 하고 싶은 유효한 UI를 표현하지 못하는 경우를 막는 거예요. (예를 들면, 에러 메시지와 비활성화된 입력창을 동시에 보여주면 안돼요. 만약 이런 UI를 보여준다면 사용자가 에러를 고칠 수 없을 거예요.)
여기 상태 변수에 대해 물어보고 싶은 몇가지 질문이 있어요.
- 이 상태가 모순을 발생시키나요? 예를 들어,
isTyping
과isSubmitting
은 모두true
가 될 수 없어요. 모순은 보통 상태가 충분히 제한되지 않았다는 것을 의미해요. 두 개의 불리안 변수는 가능한 조합이 4가지가 만들어지지만 오직 3개만이 유효한 상태가 돼요. 이 "불가능한" 상태를 제거하려면 이들을'typing'
,'submitting'
, 또는'success'
중 하나가 되는status
로 합쳐야해요. - 이미 다른 상태 변수에서 같은 정보를 사용할 수 있나요? 이는 또 다른 모순이에요.
isEmpty
와isTyping
는 동시에true
가 될 수 없어요. 이들은 분리된 상태 변수로 만들면 싱크를 벗어나서 버그를 발생시킬 위험을 가지게 돼요. 다행스럽게도 여러분은isEmpty
를 제거하고 대신answer.legnth === 0
를 확인할 수 있어요. - 다른 변수의 역으로 같은 정보를 얻을 수 있나요?
isError
는 필요하지 않아요. 그 대신에error != null
로 확인할 수 있으니까요.
이러한 청소를 마친 후에, 여러분은 (7개에서 감소하여) 3개의 상태 변수만 남겨질 거예요.
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // '입력', '제출 중' 또는 '성공'
이 상태 변수들이 필수적이라는 것을 알고 있기 때문에 기능을 바꾸지 않고서는 이들 중 그 어느것도 제거할 수 없어요.
리듀서로 "불가능한" 상태 제거하기
Step 5: Connect the event handlers to set state | 5단계: 이벤트 핸들러를 상태 세터 함수와 연결하기
마지막으로 상태를 업데이트하는 이벤트 핸들러를 만드세요. 아래는 이벤트 핸들러를 연결한 최종 폼이에요.
이 코드가 기존의 명령형의 예시보다 더 길다고 할지라도 이 코드가 더 안전해요. 상태 변화로 모든 상호 작용을 표현하는 것은 나중에 새로운 시각적 상태를 기존 상태의 오류 없이 추가하도록 해줘요. 또한 상태가 상호작용하는 로직 자체를 변경하지 않고서 각 상태에서 보여줄 내용을 바꿀 수 있어요.
Recap | 요약
- 선언형 프로그래밍은 UI를 세세하게 관리하는 명령형 프로그래밍이 아니라 각각의 시각적 상태에 대한 UI를 설명하는 것을 의미해요.
- 컴포넌트를 개발할 때는,
- 컴포넌트의 모든 시각적 상태를 식별하세요.
- 상태 변화를 사람이 발생시켰는지, 컴퓨터가 발생시켰는지를 결정해요.
useState
를 사용하여 상태를 모델링하세요.- 버그와 모순을 피하려면 불필요한 상태 변수를 제거하세요.
- 상태를 결정하는 이벤트 핸들러를 연결하세요.
Challenges | 도전 과제
1. CSS 클래스를 추가 및 제거하기
사진을 클릭하면 background--active
CSS 클래스를 밖의 <div>
에서 제거하고 <img>
에 picture--active
클래스를 추가하세요. 배경을 닷 ㅣ클릭하면 기존 CSS 클래스가 복구되어야해요.
시각적으로 여러분은 사진을 클릭하면 보라색 배경이 제거되고 사진의 경계선이 하이라이팅이 되어야 해요. 그림의 바깥쪽을 누르면 배경은 하이라이팅이 되고 사진의 경계선은 하이라이트가 제거되어야 해요.
이 컴포넌트는 두개의 시각적 상태를 가지고 있어요. 사진이 활성화가 될 때와 사진이 비활성화가 될 때에요.
- 만약 사진이 활성화 된다면 CSS 클래스는
background
와picture picture--active
가 돼요. - 만약 사진이 비활성화 된다면 CSS 클래스는
background background--active
와picture
가 돼요.
하나의 불리안 상태 변수면 사진이 활성화 되었는지를 기억하기 충분해요. 원래 과제는 CSS 클래스를 지우고 추가하는 거였어요. 그 대신 리액트에서는 UI 요소를 조작하는 대신 어떤 것을 보여주고 싶은지를 설명해야해요. 그래서 현재 상태에 따라 각 CSS 클래스를 계산해야해요. 전파를 멈춰야 이미지를 클릭하는 것이 배경을 클릭하는 것처럼 등록하지 않아요.
이미지를 클릭하고 밖을 클릭하여 이 버전이 잘 동작하는지 검증하세요.
아니면 두 개의 JSX 청크를 반환할 수도 있어요.
만약 두개의 다른 JSX 청크가 같은 트리를 설명한다면 그들의 중첩(첫 번째 <div>
→ 첫 번째 i<mg>
)도 일치해야 한다는 것을 기억하세요. 그렇지 않다면 isActive
를 토글했을 때 위쪽의 트리 전체가 재생성 되고 상태를 초기화해요. 이것이 만약 동일한 JSX 트리가 양 쪽에서 반환된다면 하나의 JSX로 작성하는 것이 나은 이유에요.
2. 프로필 에디터
기본 자바스크립트와 DOM으로 구현된 작은 폼이에요. 이들의 동작을 이해하기 위해 실행해보세요.
이 폼은 두 가지 모드가 있어요. 수정 모드에서는 입력창을 볼 수 있고 읽기 모드에서는 결과를 볼 수 있어요. 버튼의 라벨은 여러분의 모드에 따라 "Edit"와 "Save"로 변해요. 입력을 변경하면 실시간으로 아래의 환영 메시지가 업데이트돼요.
여러분의 과제는 아래의 샌드박스에서 리액트로 이를 재구현하는거예요. 여러분의 편의를 위해 마크업은 이미 JSX로 변경되어 있지만 기존의 것처럼 입력창을 보여주고 숨기는 기능은 구현해야해요.
아래의 텍스트로 업데이트 한다는 점을 잊지 마세요!
3. 명령형 코드를 리액트 없이 리팩토링하기
이전 과제의 리액트 없이 명령적으로 작성된 샌드박스에요.
리액트가 없다고 생각해보세요. 이 코드를 로직이 더 안전해지고 리액트 버전에 더 비슷한 방법으로 리팩토링 할 수 있나요? 리액트와 같이 상태가 명시적이라면 어떻게 생겼을까요?
만약 어디서부터 시작해야할지 모르겠다면 아래의 스텁 함수는 이미 대부분의 구조를 갖고 있어요. 만약 여기서 시작한다면 updateDOM
함수 안에서 잃어버린 로직을 채우세요. (필요한 곳의 기존 코드를 참조하세요.)
잃어버린 로직은 보여주는 창을 입력창과 콘텐츠로 변경해주는 로직과 라벨을 업데이트하는 로직을 포함하고 있어요.
작성한 updateDOM
함수는 리액트가 기저에서 상태를 설정할 때 무엇을 하는지를 보여주고 있어요. (그러나 리액트는 상태를 설정한 때부터 변하지 않은 DOM 속성을 건드는 것도 기피해요.)
'리액트 공식문서 | React Docs > Learn > Learn React' 카테고리의 다른 글
[Managing State] Sharing State Between Components | 컴포넌트 간 상태 공유하기 (0) | 2024.02.26 |
---|---|
[Managing State] Choosing the State Structure | 상태 구조 정하기 (2) | 2024.02.24 |
[Managing State] Managing State Overview | 상태 관리하기 개요 (0) | 2024.02.22 |
[Adding Interactivity] Updating Arrays in State | 상태에서 배열 업데이트하기 (0) | 2024.02.21 |
[Adding Interactivity] Updating Objects in State | 상태에서 객체 업데이트하기 (0) | 2024.02.21 |