상태로 입력에 반응하기
React는 UI를 조작하는 선언적인 방법을 제공합니다. UI의 개별 부분을 직접 조작하는 대신, 컴포넌트가 가질 수 있는 다양한 상태를 설명하고 사용자 입력에 따라 상태 간 전환합니다. 이는 디자이너가 UI를 생각하는 방식과 유사합니다.
배울 내용
- 선언적 UI 프로그래밍이 명령형 UI 프로그래밍과 어떻게 다른지
- 컴포넌트가 가질 수 있는 다양한 시각적 상태를 열거하는 방법
- 코드에서 다양한 시각적 상태 간 변경을 트리거하는 방법
선언적 UI와 명령형 UI 비교
UI 상호작용을 설계할 때, 아마도 UI가 사용자 동작에 어떻게변화하는지생각하게 됩니다. 사용자가 답변을 제출할 수 있는 양식을 생각해 보세요:
- 양식에 무언가를 입력하면 "제출" 버튼이활성화됩니다.
- "제출"을 누르면 양식과 버튼이 모두비활성화되고, 스피너가 표시됩니다.
- 네트워크 요청이 성공하면 양식이숨겨지고,"감사합니다" 메시지가표시됩니다.
- 네트워크 요청이 실패하면 오류 메시지가표시되고,양식이 다시활성화됩니다.
명령형 프로그래밍에서는 위 내용이 상호작용을 구현하는 방식과 직접적으로 일치합니다. 방금 일어난 일에 따라 UI를 조작하기 위한 정확한 지시사항을 작성해야 합니다. 다른 방식으로 생각해 보세요: 차에 탄 누군가 옆에 앉아서 턴 바이 턴으로 어디로 가야 하는지 알려주는 것을 상상해 보세요.

일러스트:Rachel Lee Nabors
그들은 당신이 어디로 가고 싶은지 모르고, 그저 당신의 명령을 따를 뿐입니다. (그리고 만약 방향을 잘못 알려주면 잘못된 장소에 도착하게 됩니다!) 이것을명령형이라고 부르는 이유는 스피너부터 버튼까지 각 요소에 "명령"을 내려 컴퓨터에게 UI를어떻게업데이트할지 알려야 하기 때문입니다.
이 명령형 UI 프로그래밍 예시에서, 양식은 React없이구축되었습니다. 브라우저DOM만 사용합니다:
명령적으로 UI를 조작하는 것은 격리된 예시에서는 충분히 잘 작동하지만, 더 복잡한 시스템에서는 관리하기가 기하급수적으로 더 어려워집니다. 이와 같은 다양한 양식으로 가득 찬 페이지를 업데이트하는 것을 상상해 보세요. 새로운 UI 요소나 새로운 상호작용을 추가하려면 버그를 도입하지 않았는지(예: 무언가를 표시하거나 숨기는 것을 잊는 경우) 확인하기 위해 기존의 모든 코드를 주의 깊게 검사해야 합니다.
React는 이 문제를 해결하기 위해 만들어졌습니다.
React에서는 UI를 직접 조작하지 않습니다. 즉, 컴포넌트를 직접 활성화, 비활성화, 표시 또는 숨기지 않습니다. 대신,무엇을 표시하고 싶은지 선언하면,React가 UI를 업데이트하는 방법을 알아냅니다. 택시를 타고 운전자에게 정확히 어디로 턴해야 하는지 알려주는 대신 가고 싶은 곳을 알려주는 것을 생각해 보세요. 운전사의 임무는 당신을 그곳에 데려다주는 것이며, 그들은 당신이 생각하지 못한 지름길을 알고 있을 수도 있습니다!

일러스트:Rachel Lee Nabors
선언적으로 UI 생각하기
위에서 명령적으로 양식을 구현하는 방법을 보았습니다. React에서 어떻게 생각하는지 더 잘 이해하기 위해, 아래에서 이 UI를 React로 재구현하는 과정을 살펴볼 것입니다:
- 식별컴포넌트의 다양한 시각적 상태
- 확인해당 상태 변화를 유발하는 요소
- 표현메모리 내 상태를
useState를 사용하여 - 제거 불필요한 상태 변수
- 연결상태를 설정하는 이벤트 핸들러
1단계: 컴포넌트의 다양한 시각적 상태 식별
컴퓨터 과학에서는 "상태 머신"이 여러 "상태" 중 하나에 있다는 이야기를 들을 수 있습니다. 디자이너와 함께 일한다면 다양한 "시각적 상태"에 대한 목업을 본 적이 있을 것입니다. React는 디자인과 컴퓨터 과학의 교차점에 서 있으므로, 이 두 가지 아이디어 모두 영감의 원천이 됩니다.
먼저, 사용자가 볼 수 있는 UI의 모든 다양한 "상태"를 시각화해야 합니다:
- 비어 있음: 폼에 비활성화된 "제출" 버튼이 있음.
- 입력 중: 폼에 활성화된 "제출" 버튼이 있음.
- 제출 중: 폼이 완전히 비활성화됨. 스피너가 표시됨.
- 성공: 폼 대신 "감사합니다" 메시지가 표시됨.
- 오류: 입력 중 상태와 동일하지만 추가 오류 메시지가 있음.
디자이너처럼 로직을 추가하기 전에 다양한 상태에 대한 "목업"을 만들거나 "모의"를 생성하고 싶을 것입니다. 예를 들어, 다음은 폼의 시각적 부분만을 위한 목업입니다. 이 목업은 기본값이status인 'empty'라는 prop에 의해 제어됩니다:
해당 prop을 원하는 대로 호출할 수 있으며, 이름 지정은 중요하지 않습니다.status = 'empty'를 status = 'success'로 편집하여 성공 메시지가 나타나는지 확인해 보세요. 목업을 사용하면 로직을 연결하기 전에 UI를 빠르게 반복할 수 있습니다. 다음은 동일한 컴포넌트의 더 완성된 프로토타입으로, 여전히statusprop에 의해 "제어"됩니다:
2단계: 해당 상태 변화를 유발하는 요소 확인
두 종류의 입력에 대한 응답으로 상태 업데이트를 트리거할 수 있습니다:
- 사람의 입력,예를 들어 버튼 클릭, 필드 입력, 링크 탐색.
- 컴퓨터의 입력,예를 들어 네트워크 응답 도착, 타임아웃 완료, 이미지 로딩.


일러스트레이션 제공:Rachel Lee Nabors
두 경우 모두,UI를 업데이트하려면상태 변수를 설정해야 합니다.개발 중인 폼의 경우 몇 가지 다른 입력에 대한 응답으로 상태를 변경해야 합니다:
- 텍스트 입력 변경(사용자)은 텍스트 상자가 비어 있는지 여부에 따라 상태를비어 있음 상태에서 입력 중상태로 또는 그 반대로 전환해야 합니다.
- 제출 버튼 클릭(사용자)은 상태를제출 중상태로 전환해야 합니다.
- 네트워크 응답 성공 (컴퓨터)은 상태를 성공상태로 전환해야 합니다.
- 네트워크 응답 실패(컴퓨터)은 상태를 해당 오류 메시지와 함께오류상태로 전환해야 합니다.
참고
사용자 입력은 종종 이벤트 핸들러가 필요합니다!
이 흐름을 시각화하는 데 도움이 되도록 각 상태를 라벨이 붙은 원으로, 두 상태 간의 각 변경 사항을 화살표로 종이에 그려 보세요. 이렇게 하면 많은 흐름을 스케치하고 구현하기 훨씬 전에 버그를 정리할 수 있습니다.


폼 상태
3단계: 메모리에서 상태를useState로 표현하기
다음으로 컴포넌트의 시각적 상태를 메모리에서 useState로 표현해야 합니다. 핵심은 단순함입니다: 각 상태 조각은 "움직이는 조각"이며,가능한 한 적은 수의 "움직이는 조각"을 원합니다.복잡성이 증가하면 버그도 더 많아집니다!
반드시 존재해야 하는 상태부터 시작하세요. 예를 들어, 입력에 대한answer와 마지막 오류를 저장하기 위한error(존재하는 경우)를 저장해야 합니다:
그런 다음 표시하려는 시각적 상태 중 하나를 나타내는 상태 변수가 필요합니다. 이를 메모리에서 표현하는 방법은 보통 하나 이상이므로 실험해 봐야 합니다.
최선의 방법을 즉시 생각하기 어렵다면, 가능한 모든 시각적 상태가 확실히 포함되도록 충분한 상태를 추가하는 것으로 시작하세요:
첫 번째 아이디어가 최선이 아닐 수도 있지만, 괜찮습니다. 상태 리팩토링은 과정의 일부입니다!
4단계: 불필요한 상태 변수 제거하기
상태 내용에서 중복을 피하여 필수적인 것만 추적하고 싶을 것입니다. 상태 구조를 리팩토링하는 데 약간의 시간을 투자하면 컴포넌트를 더 쉽게 이해하고, 중복을 줄이며, 의도하지 않은 의미를 피할 수 있습니다. 목표는메모리의 상태가 사용자에게 보여주고 싶은 유효한 UI를 나타내지 않는 경우를 방지하는 것입니다.(예를 들어, 오류 메시지를 표시하면서 동시에 입력을 비활성화해서는 안 됩니다. 그렇지 않으면 사용자가 오류를 수정할 수 없기 때문입니다!)
상태 변수에 대해 물어볼 수 있는 몇 가지 질문은 다음과 같습니다:
- 이 상태가 모순을 일으키나요?예를 들어,
isTyping과isSubmitting은 동시에true일 수 없습니다. 모순은 일반적으로 상태가 충분히 제약되지 않았음을 의미합니다. 두 불리언 값의 가능한 조합은 네 가지이지만, 유효한 상태에 해당하는 것은 세 가지뿐입니다. 이 "불가능한" 상태를 제거하려면, 이를 세 가지 값 중 하나여야 하는status로 결합할 수 있습니다:'typing','submitting', 또는'success'. - 동일한 정보가 이미 다른 상태 변수에 존재하나요?또 다른 모순:
isEmpty와isTyping은 동시에true일 수 없습니다. 이를 별도의 상태 변수로 만들면 동기화가 깨져 버그가 발생할 위험이 있습니다. 다행히도isEmpty를 제거하고 대신answer.length === 0을 확인할 수 있습니다. - 다른 상태 변수의 역(inverse)에서 동일한 정보를 얻을 수 있나요?
isError는 필요하지 않습니다. 대신error !== null를 확인할 수 있기 때문입니다.
이 정리 작업 후에는 3개의 (원래 7개에서 줄어든!)필수적인상태 변수가 남습니다:
이들이 필수적이라는 것은 기능을 깨뜨리지 않고는 그 중 어떤 것도 제거할 수 없다는 점에서 알 수 있습니다.
5단계: 상태를 설정하는 이벤트 핸들러 연결하기
마지막으로, 상태를 업데이트하는 이벤트 핸들러를 생성합니다. 아래는 모든 이벤트 핸들러가 연결된 최종 폼입니다:
이 코드는 원래의 명령형 예제보다 길지만 훨씬 덜 취약합니다. 모든 상호작용을 상태 변화로 표현하면 나중에 기존 상태를 깨뜨리지 않고 새로운 시각적 상태를 도입할 수 있습니다. 또한 상호작용 자체의 로직을 변경하지 않고도 각 상태에서 무엇을 표시해야 하는지 변경할 수 있습니다.
요약
- 선언형 프로그래밍은 UI를 세세하게 관리(명령형)하는 대신 각 시각적 상태에 대한 UI를 설명하는 것을 의미합니다.
- 컴포넌트를 개발할 때:
- 모든 시각적 상태를 식별합니다.
- 상태 변화를 일으키는 사람과 컴퓨터의 트리거를 결정합니다.
- 상태를
useState로 모델링합니다. - 버그와 모순을 피하기 위해 비필수적인 상태를 제거합니다.
- 상태를 설정하는 이벤트 핸들러를 연결합니다.
Try out some challenges
Challenge 1 of 3:Add and remove a CSS class #
Make it so that clicking on the picture removes the background--active CSS class from the outer <div>, but adds the picture--active class to the <img>. Clicking the background again should restore the original CSS classes.
Visually, you should expect that clicking on the picture removes the purple background and highlights the picture border. Clicking outside the picture highlights the background, but removes the picture border highlight.
