리액트는 선언적인 방식으로 UI를 조작합니다. 개별적인 UI를 직접 조작하는 것 대신에 컴포넌트 내부에 여러 state를 묘사하고 사용자의 입력에 따라 state를 변경합니다. 이는 디자이너가 UI를 바라보는 방식과 비슷합니다.
학습 내용
- 선언형 UI 프로그래밍과 명령형 UI 프로그래밍의 차이점
- 컴포넌트가 있을 수 있는 다양한 시각적 state를 열거하는 방법
- 코드에서 다른 시각적 state 간의 변경을 발동하는 방법
UI인터렉션을 디자인할 때 사용자의 행동에 따라 UI가 어떻게 변하는지에 대해서 생각해 보셨을 것입니다.
사용자가 답변을 제출할 수 있는 양식을 생각해 봅시다.
명령형 프로그래밍에서 위의 내용은 인터랙션을 구현하는 방법에 직접적으로 해당합니다. 방금 일어난 일에 따라 UI를 조작하기 위한 정확한 지침을 작성해야합니다. 다른 방식으로 생각해봅시다: 자동차를 타고 가는 사람 옆에서 어디로 가야하는지 차례대로 알려주는 상상을 해봅시다.
운전자는 사용자가 어디로 가고 싶은지 모른 채 명령만 따를 뿐입니다. (지시를 잘못 내리면 엉뚱한 곳에 가게 됩니다!) 컴퓨터에게 스피너부터 버튼까지 각 요소에 “명령”을 내려 컴퓨터에 UI를 업데이트할 내용을 지시해야 하므로, 이를 명령형이라고 부릅니다.
UI를 조작하는 것은 복잡한 시스템에서는 관리하기가 기하급수적으로 어려워집니다. 다양한 form 양식으로 가득 찬 페이지를 업데이트 해야 한다고 생각해봅시다. 새로운 UI 요소나 새로운 인터랙션을 추가하려면 기존의 모든 코드를 주의 깊게 살펴 버그의 발생 여부(예를 들어 무언가를 표시하거나 숨기는 것을 잊는 등)를 확인해야 합니다.
React는 이런 문제를 해결하기 위해 만들어졌습니다.
React에서는 직접 UI를 조작하지 않습니다. 즉, 컴포넌트를 직접 활성화하거나 비활성화 하지도, 보여주거나 숨기지도 않습니다. 대신 표시할 내용을 선언하면 React가 UI를 업데이트할 방법을 알아냅니다. 택시를 타고 기사에게 정확히 어디서 꺾어야 할지를 알려주는 대신 어디로 가고 싶은지만 말한다고 생각해 보세요. 목적지까지 데려다주는 것은 택시기사의 몫이며, 기사는 여러분이 미처 생각하지 못한 지름길을 알고 있을 수도 있습니다!
위에서 form을 명령형으로 구현하는 방법을 살펴보았습니다. 아래에서는 React로 사고하는 방법을 더 잘 이해하기 위해 이 UI를 React로 다시 구현하는 과정을 살펴봅시다:
useState
를 사용하여 메모리의 상태를 표현합니다.컴퓨터 과학에서 "상태 머신"이란 여러가지 “상태”들 중 하나라는 말을 들어보셨을 것입니다. 디자이너와 함께 일한다면 다양한 “시각적인 상태”에 대한 목업을 보셨을 것입니다. React는 디자인과 컴퓨터 과학의 교차점에 서있기 때문에 이 두 아이디어 모두 영감의 원천이 됩니다.
먼저 사용자에게 표시될 수 있는 UI의 다양한 “상태”를 모두 시각화해야 합니다:
디자이너와 마찬가지로 로직을 추가하기 전에 다양한 상태에 대한 “목업”을 만들고 싶을 것입니다.
- [DEEP DIVE] 한 번에 여러 시각적 상태 표시하기
컴포넌트에 시각적 상태가 많은 경우 한 페이지에 모두 표시하는 것이 편리할 수 있습니다:
이런 페이지를 보통 “living styleguide”또는 “storybook”이라 부릅니다.
두 종류의 입력에 대한 응답으로 상태 변경을 유발할 수 있습니다:
두 경우 모두 state 변수를 설정해야 UI를 업데이트할 수 있습니다. 개발중인 form의 경우 몇 가지 다른 입력에 따라 state를 변경해야합니다:
Note
사람의 입력에는 종종 이벤트 핸들러가 필요합니다!
이 흐름을 시각화하는 데 도움이 되도록 종이에 각 상태가 적힌 원을 그리고 각 상태 사이의 변경 사항을 화살표로 그려 보세요. 이 방식으로 흐름을 파악할 수 있을 뿐 아니라 구현하기 훨씬 전에 버그를 분류할 수 있습니다.
다음으로 메모리에서 컴포넌트의 시각적 상태를 useState로 표현해야합니다. 이 과정은 단순함이 핵심입니다. 각 상태 조각은 “움직이는 조각”이며, 가능한 적은 수의 “움직이는 조각”을 원합니다. 복잡할수록 버그가 더 많이 발생합니다!
반드시 필요한 state부터 시작하세요. 예를 들어 입력에 대한 answer를 저장하고, 가장 마지막에 발생한 error(존재한다면)도 저장해야 합니다.
그런 다음 앞서 설명한 시각적 상태 중 어떤 상태를 표시할지를 나타내는 state 변수가 필요합니다. 일반적으로 메모리에서 이를 표현하는 방법은 여러가지가 있으므로 실험해봐야 합니다.
가장 좋은 방법을 즉시 생각하기 어렵다면 가능한 모든 시각적 상태를 확실하게 다룰 수 있을 만큼 충분한 state를 추가하는 것부터 시작하세요.
처음 떠올린 생각이 최선이 아닐 수도 있지만 괜찮습니다. state를 리팩토링 하는 것도 과정의 일부이니까요!
state 콘텐츠의 중복을 피해 필수적인 것만 추적하고 싶을 것입니다. state 구조를 리팩토링하는 데 약간의 시간을 투자하면 컴포넌트를 더 쉽게 이해하고, 중복을 줄이며, 의도하지 않은 경우를 피할 수 있습니다. 목표는 state가 사용자에게 보여주기를 원하는 유효한 UI를 나타내지 않는 경우를 방지하는 것입니다. (예를 들어 오류 메세지를 표시하면서 동시에 입력을 비활성화하면 사용자는 오류를 수정할 수 없게 됩니다!)
다음은 state 변수에 대해 물어볼 수 있는 몇가지 질문입니다:
isTyping
과 isSubmitting
은 동시에 true
일 수 없습니다. 이러한 모순은 일반적으로 state가 충분히 제약되지 않았음을 의미합니다. 두 boolean의 조합은 네 가지가 가능하지만 유효한 state는 세 가지뿐입니다. “불가능한” state를 제거하려면 세 가지 값을 하나의 status로 결합하면 됩니다: 'typing'
, 'submitting'
, 'success'
.isEmpty
와 isTyping
은 동시에 true
가 될 수 없습니다. 이를 각 state 변수로 분리하면 동기화되지 않아 버그가 발생할 위험이 있습니다. 다행히 isEmpty
를 제거하고 대신 answer.length === 0
으로 확인할 수 있습니다.isError
는 error !== null
을 대신 확인할 수 있기 때문에 필요하지 않습니다.이렇게 정리하고 나면 3개(7개가 줄어든!)의 필수 state 변수만 남습니다:
이들은 기능을 망가뜨리지 않고는 어느 하나도 제거할 수 없으므로 필수 요소임을 알 수 있습니다.
- [DEEP DIVE] reducer로 “불가능한” state 제거하기
이 세 state 변수는 이 form의 상태를 충분히 잘 표현합니다. 그러나 여전히 완전히 설명되지 않는 중간 상태가 몇 가지 있습니다. 예를 들어 null이 아닌
error
는status
가'success'
일 때는 의미가 없습니다. state를 조금 더 정확하게 모델링하려면 reducer로 분리하면 됩니다. reducer를 사용하면 여러 state 변수를 하나의 객체로 통합하고 관련된 모든 로직도 합칠 수 있습니다!
마지막으로 state 변수를 설정하는 이벤트 핸들러를 생성합니다. 아래는 모든 이벤트 핸들러가 연결된 최종 form입니다:
이 코드는 원래의 명령형 예제보다 길지만 훨씬 덜 취약합니다. 모든 상호작용을 state 변화로 표현하면 나중에 기존 상태를 깨지 않고도 새로운 시각적 상태를 도입할 수 있습니다. 또한 인터랙션 자체의 로직을 변경하지 않고도 각 state에 표시되어야 하는 항목을 변경할 수 있습니다.
요약
선언형 프로그래밍은 UI를 세밀하게 관리(명령형)하지 않고 각 시각적 상태에 대해 UI를 기술하는 것을 의미합니다.
컴포넌트를 개발할 때
1. 모든 시각적 상태를 식별하세요.
2. 사람 및 컴퓨터가 상태 변화를 유발하는 요인을 결정하세요.
3. useState
로 상태를 모델링하세요.
4. 버그와 모순을 피하려면 비필수적인 state를 제거하세요.
5. 이벤트 핸들러를 연결하여 state를 설정하세요