ReactNextCentral

사용자 입력 State 처리

Published on
이 글은 리액트를 사용하여 선언적 UI 프로그래밍을 구현하는 방법과 UI 상태를 관리하는 전략에 대한 설명을 제공합니다.
Table of Contents

리액트는 UI를 조작하는 선언적인 방법을 제공합니다. 직접 UI의 개별 요소를 조작하는 대신 컴포넌트가 취할 수 있는 다양한 상태를 설명하고, 사용자 입력에 대한 응답으로 상태 변경을 전환합니다. 이는 디자이너가 UI를 생각하는 방식과 유사합니다.

배울 내용

  • 명령적인 UI 프로그래밍과 선언적인 UI 프로그래밍의 차이점
  • 컴포넌트의 다양한 시각적 상태를 열거하는 방법
  • 코드에서 다른 시각적 상태 사이의 변경을 트리거하는 방법

선언적인 UI와 명령적인 UI의 비교

UI 상호작용을 설계할 때는 사용자 동작에 따라 UI가 어떻게 변경되는지 고려할 것입니다. 사용자가 답변을 제출할 수 있는 양식을 생각해보세요.

  • 양식에 입력을 입력하면 "제출" 버튼은 활성화됩니다.
  • "제출"을 클릭하면 양식과 버튼은 비활성화되고, 대신 스피너가 표시됩니다.
  • 네트워크 요청이 성공하면 양식은 숨겨지고, "감사합니다" 메시지가 표시됩니다.
  • 네트워크 요청이 실패하면 에러 메시지가 표시되며, 양식은 다시 활성화됩니다.

명령적 프로그래밍에서는 위와 같은 내용을 상호작용을 구현하는 방식으로 직접 조작합니다. 발생한 사건에 따라 UI를 조작하는 정확한 명령을 작성해야 합니다. 예를 들어, 차 안에서 옆 사람에게 차례로 회전 방향을 알려주는 것과 유사합니다.

상태를 업데이트하는 코드만 작성하면 되며, 스피너에서 버튼까지 각 요소를 "명령"하여 컴퓨터에게 UI를 업데이트하는 방법을 알려줍니다.

명령적인 UI 프로그래밍 예제에서는 리액트를 사용하지 않고, 브라우저 DOM만 사용하여 양식을 구축합니다.

Edit practical-kepler-wh03mi

명령적인 방식으로 UI를 조작하는 것은 독립된 예제에서는 잘 작동하지만, 더 복잡한 시스템에서는 관리하기가 기하급수적으로 어려워집니다. 이와 같은 양식이 포함된 페이지를 업데이트해야 한다면 어려움을 겪을 수 있습니다. 새로운 UI 요소나 상호작용을 추가하려면 기존의 모든 코드를 주의 깊게 확인하여 버그가 없는지 확인해야 합니다(예: 누락된 표시 또는 숨김).

리액트는 이러한 문제를 해결하기 위해 만들어졌습니다.

리액트에서는 UI를 직접 조작하지 않습니다. 즉, 컴포넌트를 활성화, 비활성화, 표시 또는 숨김하는 등의 작업을 직접 수행하지 않습니다. 대신 표시할 UI를 설명하고, 리액트가 UI를 업데이트하는 방법을 결정합니다. 차에 타고 가서 목적지를 명확하게 알려주는 대신 어디로 가고 싶은지 말해주는 것입니다. 운전자의 역할은 목적지에 도달하는 것이며, 그들은 고려하지 못한 일부 지름길을 알 수도 있습니다!

선언적인 UI에 대해 생각하기

위에서 명령적인 방식으로 양식을 구현하는 방법을 살펴보았습니다. 리액트로 생각하는 방법을 이해하기 위해 아래에서 리액트로 이 UI를 다시 구현해보겠습니다.

  1. 컴포넌트의 다른 시각적 상태를 확인합니다.
  2. 어떤 입력에 의해 상태 변경이 트리거되는지 확인합니다.
  3. useState를 사용하여 상태를 메모리에 표현합니다.

단계 1: 컴포넌트의 다른 시각적 상태 확인

컴퓨터 과학에서 "상태 기계"가 여러 "상태" 중 하나에 있는 것을 들어본 적이 있을 것입니다. 디자이너와 함께 일하는 경우 다른 "시각적 상태"에 대한 모킹(mocking)을 볼 수 있을 것입니다. 리액트는 디자인과 컴퓨터 과학의 교차점에 위치하기 때문에 두 가지 아이디어 모두 영감의 원천입니다.

먼저 사용자가 볼 수 있는 UI의 다양한 "상태"를 시각화해야 합니다.

  • Empty: 양식에 비활성화된 "제출" 버튼이 있습니다.
  • Typing: 양식에 활성화된 "제출" 버튼이 있습니다.
  • Submitting: 양식이 완전히 비활성화되었고, 스피너가 표시됩니다.
  • Success: 양식 대신 "감사합니다" 메시지가 표시됩니다.
  • Error: 추가로 에러 메시지가 있는 Typing 상태와 동일합니다.

디자이너처럼 UI의 다른 상태에 대한 "모킹" 또는 "모형"을 만들어 로직을 추가하기 전에 빠르게 UI를 반복해볼 수 있습니다. 예를 들어, 여기는 양식의 시각적 부분에 대한 모킹입니다. 이 모킹은 'status'라는 기본값이 'empty'인 속성에 의해 제어됩니다.

Edit wild-water-ewr4uj

속성의 이름은 중요하지 않습니다. status = 'empty'status = 'success'로 편집하여 성공 메시지가 나타나는지 확인해보세요. 모킹을 통해 로직을 연결하기 전에 UI를 빠르게 반복할 수 있습니다. 여기에 더 구체적인 프로토 타입이 있습니다. 여전히 'status' prop에 의해 "제어"됩니다.

Edit determined-jones-pmnojw

자세히 알아보기: 여러 시각적 상태를 동시에 표시하기

컴포넌트에 많은 시각적 상태가 있는 경우 모든 상태를 한 페이지에 표시하는 것이 편리할 수 있습니다.

Edit peaceful-leaf-f0p7sj

이와 같은 페이지는 종종 "리빙 스타일 가이드" 또는 "스토리북"이라고 불립니다.

단계 2: 상태 변경을 트리거하는 입력 확인

두 종류의 입력에 응답하여 상태 업데이트를 트리거할 수 있습니다.

  • 사용자 입력: 버튼 클릭, 필드 입력, 링크 탐색과 같은 작업입니다.
  • 컴퓨터 입력: 네트워크 응답 도착, 타임아웃 완료, 이미지 로딩과 같은 작업입니다.

두 경우 모두 상태 변수를 설정하여 UI를 업데이트해야 합니다. 개발 중인 양식에서는 다음과 같은 몇 가지 다른 입력에 응답하여 상태를 변경해야 합니다.

  • 텍스트 입력 변경(사용자 입력)은 상자가 비어 있는지 여부에 따라 Empty 상태에서 Typing 상태로 전환하거나 그 반대로 전환해야 합니다.
  • 제출 버튼 클릭(사용자 입력)은 Submitting 상태로 전환해야 합니다.
  • 네트워크 응답 성공(컴퓨터 입력)은 Success 상태로 전환해야 합니다.
  • 네트워크 응답 실패(컴퓨터 입력)는 Error 상태와 해당하는 에러 메시지로 전환해야 합니다.

참고
사용자 입력은 종종 이벤트 핸들러가 필요합니다!

이 흐름을 시각화하기 위해 각 상태를 레이블이 지정된 원으로 종이에 그리고 두 상태 사이의 각 변경을 화살표로 그릴 수 있습니다. 이렇게 여러 흐름을 스케치하고 구현 이전에 버그를 해결할 수 있습니다.

단계 3: useState를 사용하여 상태를 메모리에 표현

이제 컴포넌트의 시각적 상태를 useState를 사용하여 메모리에 표현해야 합니다. 간단함이 가장 중요합니다. 각각의 상태는 "움직이는 부분"이며, "움직이는 부분"을 최소화해야 합니다. 복잡성이 증가하면 버그가 더 많이 발생합니다!

먼저 반드시 필요한 상태를 저장해야 합니다. 예를 들어, 입력에 대한 답변과 오류(있을 경우)를 저장해야 합니다.

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);

처음 생각한 방법이 최선의 방법이 아닐 수 있지만 괜찮습니다. 상태를 리팩토링하는 것은 과정의 일부입니다!

단계 4: 필요하지 않은 상태 변수 제거

상태 내용에서 중복을 피하려면 핵심적인 것만 추적하여 필요한 상태만 저장하도록 해야 합니다. 상태 구조를 리팩토링하는 시간을 투자하면 컴포넌트를 이해하기 쉽게 만들 수 있으며, 중복을 줄이고 의도하지 않은 의미를 방지할 수 있습니다. 목표는 메모리에 있는 상태가 사용자가 보기를 원하는 유효한 UI를 나타내지 않는 경우를 방지하는 것입니다(예: 에러 메시지를 표시하고 입력을 비활성화하면 사용자가 오류를 수정할 수 없게 됩니다!).

다음은 상태 변수에 대해 물어볼 수 있는 몇 가지 질문입니다.

  • 이 상태는 모순을 일으키는가요? 예를 들어, isTyping과 isSubmitting은 둘 다 true일 수 없습니다. 모순은 상태가 충분히 제한되지 않은 것을 의미합니다. 두 개의 부울 값에 대한 네 가지 가능한 조합이 있지만, 유효한 상태는 세 가지뿐입니다. "불가능한" 상태를 제거하기 위해 이들을 typing, submitting 또는 success 중 하나인 status로 결합할 수 있습니다.
  • 동일한 정보를 이미 다른 상태 변수에서 사용할 수 있나요? 또 다른 모순: isEmpty와 isTyping은 동시에 true일 수 없습니다. 이들을 별도의 상태 변수로 만들면 동기화 문제가 발생하여 버그가 발생할 수 있습니다. 다행히 isEmpty를 제거하고 answer.length === 0을 확인할 수 있습니다.
  • 다른 상태 변수의 반대에서 동일한 정보를 얻을 수 있나요? isErrorerror !== null을 확인하는 대신에는 필요하지 않습니다.

이 정리 작업 이후에는 7개에서 3개의 (감소된) 필수 상태 변수가 남게 됩니다.

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);

// 'typing', 'submitting', 또는 'success'
const [status, setStatus] = useState('typing'); 

단계 5: 이벤트 핸들러를 연결하여 상태 설정

마지막으로 상태를 업데이트하는 이벤트 핸들러를 만듭니다. 아래에는 모든 이벤트 핸들러가 연결된 최종 양식이 있습니다.

Edit gallant-sun-vpttuv

이 코드는 초기 명령적인 예제보다 길지만 훨씬 견고합니다. 모든 상호작용을 상태 변경으로 표현하면 새로운 시각적 상태를 손쉽게 추가할 수 있고, 기존 상태를 깨뜨리지 않고 각 상태에 표시할 내용을 변경할 수 있습니다.

요약

  • 선언적 프로그래밍은 UI의 각 시각적 상태를 설명하므로 UI를 세밀하게 관리(명령적인)하는 것과 다릅니다.
  • 컴포넌트를 개발할 때:
    1. 시각적 상태를 모두 식별합니다.
    2. 상태 변경을 위한 사용자 및 컴퓨터 트리거를 결정합니다.
    3. useState를 사용하여 상태를 모델링합니다.
    4. 버그와 모순을 피하기 위해 비필수 상태를 제거합니다.
    5. 이벤트 핸들러를 연결하여 상태를 설정합니다.