본문 바로가기

Web + APP/React

[리액트 스터디] - Reacting to Input with State

반응형
SMALL

리액트는 UI를 직접 조작하는거 대신 State를 따라서 UI를 변경하게 된다.

선언형 UI와 명령형 UI 비교

명령형 프로그래밍이 뭘까 ?

 

예시를 사용자가 폼을 제출한다고 했을 때로 하나 들어보자

 

- 폼에 무언가를 입력하면 "제출" 버튼이 활성화될 것

- "제출" 버튼을 누르면 폼과 버튼이 비활성화되고 스피너가 나탈 것

- 네트워킹 요청이 성공하면 폼은 숨겨질 것이고, "감사합니다." 메시지가 나타날 것

- 네트워크 요청이 실패하면 오류 메시지가 보일 것이고 폼은 다시 활성화될 것

 

위 요청대로 개발을 하면 명령형 프로그래밍

 

정확한 지침에 따라 코드를 자겅해야 JS가 정상적인 동작을 하게 되는 구조

 

옆에서 나불나불 거리면 화나기 마련

 

UI가 복잡해질수록 구현 난이도가 어려워짐

 

그러니 UI 조작은 리액트에게 맡기고, 무엇을 보여주고 싶은지만 선언하도록 하자.

 

마치 택시를 타서 "판교 카카오 아지트로 가주세요"라고 말해주면 되는거 처럼

알아서 해주십쇼 오마카세

UI를 선언적인 방식으로 생각하기

리액트처럼 생각하기 위해선 어떻게 하면 될까 ?

 

1. 컴포넌트의 state를 확인해라

2. 무엇이 state를 트리거하는지 알아내라

3. useState를 사용해서 메모리에 state를 표현해라

4. 불필요한 state를 제거해라

5. state 설정을 이벤트 핸들러에 연결해라

 

결국 모든 길은 state로 통한다.

 

첫 번째: 컴포넌트의 다양한 시각적 state 확인하기

어떻게 state 중심적 사고를 가질 수 있을까 ?

 

사용자가 볼 수 있는 UI의 모든 "state"를 시각화하는게 우선이다.

이걸 시각화해볼까 ?

 

- Empty: 폼은 비활성화된 "제출" 버튼을 가지고 있다.

- Typing: 폼은 활성화된 "제출" 버튼을 가지고 있다.

- Submitting : 폼은 완전히 비활성화되고 스피너가 보인다.

- Success: 폼 대신에 "감사합니다" 메시지가 보인다.

- Error: "Typing" state와 동일하지만 오류 메시지가 보인다.

 

로직대로 구현을 생각하지말고 state 중심적으로 생각하자.

 

많은 시각적 state를 한 번에 보여주기

컴포넌트가 많은 state를 가지고 있다면 한 페이제에서 모두 보여주는 것도 가능

 

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 styleguides / storybooks"라고 부른다고 한다.

 

두 번째: 무엇이 state 변화를 트리거하는지 알아내기

 

두 종류의 인풋이 있음

 

- 버튼을 누르거나, 필드를 입력하거나, 링크를 이동하는 휴먼 인풋

- 네트워크 응답이 오거나, 타임아웃이 되거나, 이미지를 로딩하거나 하는 컴퓨터 인풋

 

두 가지 경우 모두 UI를 업데이트하기 위해서는 state 변수를 설정해야함. 폼의 경우엔 입력에 따라 state를 변경함

 

- 텍스트 인풋을 변경하면, 텍스트가 있는지 없는지 여부에 따라 상태를 변경해야함. (휴먼 인풋)

- 제출 버튼을 클릭하면, Submitting state를 변경해야함 (휴먼 인풋)

- 네트워크 응답이 성공적으로 오면 Success state를 변경해야함 (컴퓨터 인풋)

- 네트워크 요청이 실패하면 Error state를 변경해야함 (컴퓨터 인풋)

 

이와 같은 흐름은 그림으로도 표현이 가능함. 디버깅이 쉬워지니 도전해볼 것

 

세 번째: 메모리의 state를 useState로 표현하기

useState를 이용해서 컴포넌트에 state를 박아넣자.

 

적을 수록 좋음 ;

 

처음에 완벽할 필요는 없음. 하다보면 고치게 됨

 

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

 

네 번째: 불필요한 state 변수를 제거하기

어떤 state를 지워야할까 ??

 

state가 사용자에게 유효한 UI를 보여주지 않는 경우이다.

 

아래 세 가지 질문을 하여 유효하지 않는 state를 걸러내자.

 

- state가 역설을 일으키는가 ? => isTyping / isSubmitting이 동시에 true일 수 없음. 이 경우 status type을 만들자.

- 다른 state 변수에 이미 같은 정보가 담겨져 있는가 ? => isEmpty와 isTyping이 동시에 true가 될 수 없다. (answer.length === 0으로 확인 가능)

- 다른 변수를 뒤집었을 때 같은 정보를 얻을 수 있는가 ? => isError는 error !== null로 확인이 가능하다.

 

// 얘들을
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);

// 이렇게 수정이 가능
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'

 

Reducer를 사용하여 "불가능한" state 제거

error가 null이 아닌데, status가 success인 것은 말이 되지 않음. 이런 경우 리듀서로 분리할 수 있음. 이는 다음 사람이 해주십쇼.

 

다섯 번째: state 설정을 위해 이벤트 핸들러를 연결하기

 

State를 사용해 Input 다루기 – React

The library for web and native user interfaces

ko.react.dev


지금까지 명령형 프로그래밍이 아닌 선언형 프로그래밍을 짜봤는데, 견고함을 느낄 수 있음

 

로직 자체를 변경하지 않고도 각각의 state에 표시되는 항목을 변경할 수 있다는 점이 강점임.

 


 

State를 사용해 Input 다루기 – React

The library for web and native user interfaces

ko.react.dev

첫번째

import {useState} from 'react';

export default function Picture() {
  const [status, setStatus] = useState(true);

  function handleEvent(e) {
    e.preventDefault();

    setStatus(!status);
  }
  
  return (
    <div className={`background ${status ? 'background--active' : ''}`}>
      <img
        onClick={handleEvent}
        className={`picture ${status ? '' : 'picture--active'}`}
        alt="Rainbow houses in Kampung Pelangi, Indonesia"
        src="https://i.imgur.com/5qwVYb1.jpeg"
      />
    </div>
  );
}

 

두번째

function handleFormSubmit(e) {
  e.preventDefault();
  if (button.textContent === 'Edit Profile') {
    button.textContent = 'Save Profile';
    hide(firstNameText);
    hide(lastNameText);
    show(firstNameInput);
    show(lastNameInput);
  } else {
    button.textContent = 'Edit Profile';
    hide(firstNameInput);
    hide(lastNameInput);
    show(firstNameText);
    show(lastNameText);
  }
}

function handleFirstNameChange() {
  firstNameText.textContent = firstNameInput.value;
  helloText.textContent = (
    'Hello ' +
    firstNameInput.value + ' ' +
    lastNameInput.value + '!'
  );
}

function handleLastNameChange() {
  lastNameText.textContent = lastNameInput.value;
  helloText.textContent = (
    'Hello ' +
    firstNameInput.value + ' ' +
    lastNameInput.value + '!'
  );
}

function hide(el) {
  el.style.display = 'none';
}

function show(el) {
  el.style.display = '';
}

let form = document.getElementById('form');
let profile = document.getElementById('profile');
let editButton = document.getElementById('editButton');
let firstNameInput = document.getElementById('firstNameInput');
let firstNameText = document.getElementById('firstNameText');
let lastNameInput = document.getElementById('lastNameInput');
let helloText = document.getElementById('helloText');
form.onsubmit = handleFormSubmit;
firstNameInput.oninput = handleFirstNameChange;
lastNameInput.oninput = handleLastNameChange;

 

이거를 reactic하게 만들어보자

 

import {useState} from 'react';

export default function EditProfile() {
  const [isEdit, setIsEdit] = useState(false);
  const [firstName, setFirstName] = useState('Jane');
  const [lastName, setLastName] = useState('Jacob');

  function handleEvent(e) {
    e.preventDefault();

    setIsEdit(!isEdit);
  }

  function changeFirstName(e) {
    setFirstName(e.target.value);
  }
  
  function changeLastName(e) {
    setLastName(e.target.value);
  }
  
  return (
    <form>
      <label>
        First name:{' '}
        {isEdit ? <input value={firstName} onChange={changeFirstName} /> : <b>{firstName}</b>}
      </label>
      <label>
        Last name:{' '}
        {isEdit ? <input value={lastName} onChange={changeLastName}/> : <b>{lastName}</b>}
      </label>
      <button onClick={handleEvent}>
        {isEdit ? 'Edit Profile' : 'Save Profile'}
      </button>
      <p><i>Hello, {firstName} {lastName}!</i></p>
    </form>
  );
}

 

세 번째

let firstName = 'Jane';
let lastName = 'Jacobs';
let isEditing = false;

function handleFormSubmit(e) {
  e.preventDefault();
  setIsEditing(!isEditing);
}

function handleFirstNameChange(e) {
  setFirstName(e.target.value);
}

function handleLastNameChange(e) {
  setLastName(e.target.value);
}

function setFirstName(value) {
  firstName = value;
  updateDOM();
}

function setLastName(value) {
  lastName = value;
  updateDOM();
}

function setIsEditing(value) {
  isEditing = value;
  updateDOM();
}

function updateDOM() {
  if (isEditing) {
    button.textContent = 'Save Profile';
    // TODO: 인풋을 보여주고 텍스트는 숨깁니다.
    hide(lastNameText);
    hide(firstNameText);
    show(lastNameInput);
    show(firstNameInput)
  } else {
    button.textContent = 'Edit Profile';
    // TODO: 인풋을 숨기고 텍스트를 보여줍니다.
    hide(lastNameInput);
    hide(firstNameInput);
    show(lastNameText);
    show(firstNameText);
  }
  // TODO: 텍스트 라벨을 업데이트합니다. (이거 뭔 소린지 몰라서 답 봤음)
  firstNameText.textContent = firstNameInput.value;
  lastNameText.textContent = lastNameInput.value;
  helloText.textContent = (
    'Hello ' +
    firstNameInput.value + ' ' +
    lastNameInput.value + '!'
  );
}

function hide(el) {
  el.style.display = 'none';
}

function show(el) {
  el.style.display = '';
}

let form = document.getElementById('form');
let profile = document.getElementById('profile');
let editButton = document.getElementById('editButton');
let firstNameInput = document.getElementById('firstNameInput');
let firstNameText = document.getElementById('firstNameText');
let lastNameInput = document.getElementById('lastNameInput');
let helloText = document.getElementById('helloText');
form.onsubmit = handleFormSubmit;
firstNameInput.oninput = handleFirstNameChange;
lastNameInput.oninput = handleLastNameChange;

해당 장이 왜 react가 탄생했는지 전반적으로 알려주는 장이라 중요해보였음

 

스칼렛이 옛날엔 jquery로 포 뜨듯이 개발했다고 했는데, 어떻게 했나 싶음 ;

반응형
LIST