본문 바로가기

이것저것 스터디📚/React - 공식 문서

React 공식문서 - Reacting to Input with State(state로 입력에 반응하기)

How declarative UI compares to impreative(선언형 UI와 명령형 UI 차이점)

React에서는 컴포넌트를 직접 활성화하거나 비활성화 하는 등 UI를 직접 조작하는 것 대신 표시할 내용을 선언하면 React가 UI를 업데이트할 방법을 알아낸다.

 


Thinking about UI declaratively(UI를 선언적인 방식으로 생각하기)

React에서 UI를 다루는 과정

1. 컴포넌트의 다양한 시각적 상태를 식별한다.

2. 상태 변화를 촉발하는 요소를 파악한다.

3. useState를 사용하여 메모리의 상태를 표현한다.

4. 비필수적인 state 변수를 제거한다.

5. 이벤트 핸들러를 연결하여 state를 설정한다.

 

Step 1 : Identify your component's different visual states(컴포넌트의 다양한 시각적 상태 식별하기)

 

가장 먼저 사용자에게 표시될 수 있는 UI의 다양한 "상태"를 모두 시각화해야한다.

 

예시) form을 만든다고 가정했을 때,

* 비어있음 : form의 "Submit" 버튼의 비활성화

* 입력중 : form의 "Submit"버튼의 활성화

* 제출중 : form은 완전히 비활성화되어있고 Spinner 표시

* 성공시 : form 대신 "Thank you" 메시지 표시

* 실패시 : "입력중" 상태와 동일하지만 추가로 오류 메시지 표시

 

위와 같은 status들을 prop으로 전달하여 컴포넌트를 prop에 의해 "제어"되게 할 수 있다.

 

* Displaying many visula states at once (한번에 여러 시각적 상태 표시하기)

import Form from './Form.js';

let statuses = [
  'empty',
  'typing',
  'submitting',
  'success',
  'error',
];

export default function App() {
  return (
    <>
      {statuses.map(status => (
        <section key={status}>
          <h4>Form ({status}):</h4>
          <Form status={status} />
        </section>
      ))}
    </>
  );
}

컴포넌트에 시각적 상태가 많은 경우 한 페이지에 모두 표시하는 것이 편리할 수 있다. 이러한 페이지를 보통 "living styleguide" 또는 "storybook"이라고 부른다.


Step 2 : Determine what triggers those state changes(상태 변경을 촉발하는 요인 파악하기)

 

위 예제에서 사용자의 입력(버튼 클릭, 필드 입력, 링크 이동 등)과 컴퓨터의 입력(네트워크에서의 응답 도착, 시간 초과, 이미지 로딩 등)이라는 두 종류의 입력에 대한 응답으로 상태 변경을 촉발할 수 있고, 두 경우 모두 state 변수를 설정해야 UI를 업데이트 할 수 있다.

 

예시) form을 만든다고 가정했을 때,

* text 입력을 변경(사람)하면 text box가 비어있는지 여부에 따라 비어있음 state에서 입력중 state 또는 그 반대로 전환

* 제출 버튼을 클릭(사람)하면 제출중 state로 전환

* 네트워크 응답 성공(컴퓨터)시 성공 state로 전환

* 네트워크 요청 실패(컴퓨터)시 일치하는 오류 메시지와 함께 오류 state로 전환

Form states

위와 같은 상태의 흐름을 시각화하면 상태의 흐름을 파악할 수 있고 버그를 분류하는데 도움이 된다.


Step 3 : Represent the state in memory with useState(메모리의 상태를 useState로 표현하기)

 

다음으로는 메모리에 컴포넌트의 시각적 상태를 useState로 표현해야 한다. 각 상태 조각은 "움직이는 조각"이며 조각이 많은수록 버그 발생 가능성이 높아진다.

 

*위 예제에서 구분한 상태를 useState로 표현한 예시

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 (비필수적인 state 변수 제거하기)

 

state가 사용자에게 보여주기를 원하는 유효한 UI를 나타내지 않는 경우를 방지하는 것이 목표이다.(예를 들어, 오류 메시지를 표시하면서 동시에 입력을 비활성화하면 사용자는 오류를 수정할 수 없게 된다.)

 

* 위 예제의 state 변수에 대한 질문

1. state가 모순을 야기하고 있는지

isTyping과 isSubmitting은 동시에 true 일 수 없다. 두 상태의 조합은 네 가지가 가능하지만 유효한 state는 세가지이다. 따라서, "불가능한" state를 제거하려면 세 가지 값을 하나의 status로 결합하면 된다("typing", "submitting", "success")

 

2. 다른 state 변수에 이미 같은 정보가 있는지

isEmpty와 isTyping은 동시에 true가 될 수 없다. 따라서 isEmpty를 제거하고 대신 answer.length === 0 으로 확인할 수 있다.

 

3. 다른 state 변수를 뒤집으면 동일한 정보를 얻을 수 있는지

isError는 error !== null을 대신 확인할 수 있기 때문에 필요하지 않다.

 

즉, 내용으로 state 변수를 정리하면 3개의 필수 state 변수만 남는다.

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

Step 5 : Connect the event handlers to set state(이벤트 핸들러를 연결하여 state 설정하기)

 

마지막으로 state 변수를 설정하는 이벤트 핸들러를 생성한다.

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  if (status === 'success') {
    return <h1>That's right!</h1>
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={
          answer.length === 0 ||
          status === 'submitting'
        }>
          Submit
        </button>
        {error !== null &&
          <p className="Error">
            {error.message}
          </p>
        }
      </form>
    </>
  );
}

function submitForm(answer) {
  // Pretend it's hitting the network.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== 'lima'
      if (shouldError) {
        reject(new Error('Good guess but a wrong answer. Try again!'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

* 참고 : React 공식문서(https://react-ko.dev/learn)