https://react.dev/learn/reacting-to-input-with-state를 보고 정리한다.

 

React는 선언적 방법을 사용하여 UI를 조작한다. 이는 디자이너가 UI에 대해 생각하는 방식과 유사하다.

  • UI를 직접 조작하지 않는다.
  • 컴포넌트는 존재할 수 있는 다양한 상태를 가진다. 이를 이용해 사용자 입력에 대한 응답으로 상태를 바꾼다.

 

선언적(declarative) UI vs 명령형(imperative) UI

명령형 프로그래밍은 현재 발생한 상황에 따라 UI를 조작하기 위해 정확한 가이드라인을 작성해야 한다.

만약 답변을 제출하는 양식을 만들 때 명령적으로 구성하면 다음과 같이 생각할 수 있다.

  • 양식에 무언가를 입력하면 "제출" 버튼 이 활성화됩니다.
  • "제출"을 누르면 양식과 버튼 이 모두 비활성화되고 스피너 가 나타납니다.
  • 네트워크 요청이 성공하면 양식 이 숨겨지고 "감사합니다"라는 메시지 가 나타납니다.
  • 네트워크 요청이 실패하면 오류 메시지 가 나타나고 양식 이 다시 활성화됩니다.

즉, 명령형 프로그래밍은 구성 요소마다 명령(활성화/비활성화 등..)하여 컴퓨터에 UI를 업데이트하는 방법 알려줘야 한다.

 

UI를 직접 조작하는 것은 복잡한 시스템에서는 관리하기가 기하급수적으로 어려워진다. 이 문제를 해결하기 위해 React가 만들어졌다.

  • 만약 새로운 UI 요소나 상호작용을 추가하려면 버그가 발생하지 않는지 모든 기존 코드를 주의 깊게 확인해야 한다.

 

React는 선언적 프로그래밍을 사용한다. 즉, 무엇을 보여줄지 선언하고, UI를 업데이트하는 방법은 React가 알아내서 처리한다.

UI를 선언적으로 생각하기

1단계 : 컴포넌트의 다양한 시각적인 state를 식별한다.

React는 두 아이디어(컴퓨터 과학, design)를 통해 영감을 얻었다.

  • 컴퓨터 과학 : state machine 개념
  • design : 다양한 "visual states"에 대한 목업 개념

따라서 사용자가 볼 수 있는 UI의 다양한 "state"를 식별해야 한다.

답변을 제출하는 양식에서는 아래와 같은 state를 식별할 수 있다.

  • 비어 있음 : 양식에 비활성화된 "제출" 버튼이 있습니다.
  • 입력 : 양식에 "제출" 버튼이 활성화되어 있습니다.
  • 제출 중 : 양식이 완전히 비활성화되었습니다. 스피너가 표시됩니다.
  • 성공 : 폼 대신 "감사합니다" 메시지가 출력됩니다.
  • 오류 : 입력 상태와 동일하지만 추가 오류 메시지가 있습니다.

logic을 추가하기 전에 다양한 상태에 대해 mocking을 만들어 확인할 수도 있다.

아래 예제에서는 state 값(empty/success)을 변경해서 올바르게 표시되는지 확인할 수 있다.

 

 

한 번에 많은 시각적인 state 표시하기

많은 state가 존재하는 경우, 한 페이지에 모두 표시하는 것이 편할 수 있다.

 

2단계 : 무엇이 state를 변경시키는지 파악한다.

state 업데이트를 트리거할 수 있는 것은 두 가지가 존재한다. 두 가지 모두 UI를 업데이트하기 위해 state 변수를 변경해야 한다.

  • Human inputs : 버튼 클릭, 필드 입력, 링크 탐색...
  • Computer inputs : 네트워크 응답 도착, 시간 초과, 이미지 로드...

<예>

아래와 같은 입력이 있고, 입력에 대한 응답으로 state를 변경할 수 있다.

  • 텍스트 입력(human) : text box(empty or not)에 따라 Empy or Typing state로 전환
  • 제출 버튼(human) : 클릭시 Submitting state로 전환
  • 성공적인 네트워크 응답(컴퓨터)  : Success state로 전환
  • 실패한 네트워크 응답(컴퓨터) : 에러 메시지와 함께 Error state로 전환

state(원)와 이를 변화시키는 입력(화살표)을 시각화하면 아래와 같다. 구현하기 전에 flow를 스케치하면 혹시 모를 버그를 찾을 수 있다.

3단계 : useState를 사용해 메모리에 state를 표현한다.

키포인트는 단순함이다. : 가능한 가장 적은 state로 구성되도록 해야 한다. 복잡할수록 더 많은 버그가 발생한다!

따라서 반드시 있어야 하는 state부터 시작해야 한다.

<예> - 총 7개 생성

  1. input을 위한 answer와 마지막 오류를 저장하기 위한 error가 필요하다.
  2. 1단계에서 식별한 시각적인 state를 표시한다. 이를 표현하는 방법은 여러 가지인데, 가장 좋은 방법이 생각나지 않는다면 모든 상태를 추가하는 것으로 시작하고 그 후에 리팩터링 하도록 한다.
    • isEmpty, isTyping, isSubmitting, isSuccess, isError

4단계 : 불필요한 state 변수를 제거한다.

state의 내용이 중복되지 않게 필수적인 것만 추적해야 한다.

여기서 목표는 저장한 state가 사용자에게 제대로 된 UI를 표현하지 못하는 경우를 막는 것이다.

불필요한 state 변수를 제거하기 위해 아래의 질문들에 대해 생각해보자

  • state가 역설을 일으키는가(모순이 존재하는 가)?
    • isTyping, isSubmitting은 같이 true가 될 수 없다. 그래서 2가지 boolean은 4가지 조합이 나오지만, 여기서는 3가지만 가능하다.
    • 불가능한 state(위에 설명)를 지우기 위해 하나의 state에 아래 3가지 state 중 하나만 설정되도록 할 수 있다.
      • 'typing', 'submitting', 'success'.
  • state 변수 간에 중복 정보가 존재하는가?
    • isTyping, isEmpty은 같이 true가 될 수 없다. 이들을 별도의 state 변수로 만들면 동기화되지 않고 버그가 발생할 위험이 있다. 
    • isEmpty를 제거하고 answer.length === 0을 체크하도록 대신할 수 있다.
  • 다른 state 변수를 뒤집으면 동일한 정보를 얻을 수 있는가?
    • isErrorerror != null로 대신할 수 있다.

위의 내용을 정리하면 아래처럼 7 -> 3개로 정리된다.

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'

5단계 : state를 설정하기 위해 이벤트 핸들러와 연결한다.

마지막으로 이벤트 핸들러를 생성하여 상태 변수를 설정한다. 다음은 모든 이벤트 핸들러가 연결된 최종 형식이다.

 

이 코드는 원래 명령형 예제보다 길지만 훨씬 덜 취약하다. 모든 상호 작용을 상태 변경으로 표현하면 나중에 기존 상태를 손상시키지 않고 새로운 시각적 상태를 도입할 수 있다. 또한 상호 작용 자체의 로직을 변경하지 않고도 각 상태에 표시되어야 하는 내용을 변경할 수 있다.

 

Challeges

몰랐던 개념이나 다르게 푼 경우, 기록을 남긴다.

1. 이벤트 버블링 막기 : e.stopPropagation() 사용

2. form submit 시 기본 동작 막기(전체 페이지를 다시 로드) : e.preventDefault()

 

+ Recent posts