Adding Interactivity - State: A Component's Memory

hyocho·2023년 6월 3일
3

React

목록 보기
21/24
post-thumbnail

컴포넌트는 상호작용의 결과로 화면을 변경시켜야 할 때가 자주 있다. 폼에 입력하는 것은 인풋 영역을 업데이트 해야하고, 이미지 슬라이드의 "다음" 버튼을 클릭하면 보여지는 화면이 바뀌어야 하고, "구매하기" 를 누르면 쇼핑카트에 상품을 넣을 수 있어야 한다. 컴포넌트는 현재 입력 값, 이미지, 쇼핑카트를 "기억할" 필요가 있다. 리액트에서 이런 컴포넌트별 메모리를 상태(state)라고 한다.

배울 것

  • useState 훅을 사용하여 상태 변수를 등록하는 방법
  • useState 훅의 반환값으로 나오는 값은 어떤 쌍인지
  • 하나 이상의 상태 변수를 더하는 방법
  • 상태가 로컬에서 호출되어야 하는 이유

When a regular variable isn’t enough

sculpture 이미지를 렌더링하는 컴포넌트가 있다. "다음" 버튼을 누르면 index1에서 2로 바꾸면서 다음 sculpture 이미지를 보여줘야 한다. 하지만 이것은 아래는 작동하지 않을 것이다.

//@App.js
import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

handleClick 이벤트 핸들러는 지역 변수인 index를 업데이트 한다. 하지만 두 가지가 이러한 변화가 보여지는 것을 막는다.
1. 지역 변수는 렌더링 사이에 유지되지 않는다. 컴포넌트가 두번째로 렌더링 될 때, 로컬 변수의 변경 사항을 고려하지 않고 처음부터 렌더링한다.
2. 지역 변수를 변화시키는 것은 렌더링을 일으키지 않는다. 리액트는 새로운 데이터로 컴포넌트를 렌더링시켜야 하는 것을 알지 못한다.

새로운 데이터로 컴포넌트를 업데이트 하려면 두 가지가 필요하다.
1. 렌더링 간에 데이터를 유지할 것.
2. 새로운 데이터로 컴포넌트를 렌더링하도록 트리거할 것

useState 훅은 이 두 가지를 제공한다.
1. 렌더링 간에 데이터를 유지하기 위한 상태 변수이다.
2. 변수를 업데이트하고 컴포넌트를 다시 렌더링하게 하는 상태 설정 함수.

Adding a state variable

상태 변수를 추가하기 위해 useState를 파일 최상단에서 import한다.

import { useState } from 'react';

그리고 아래 줄을

let index = 0;

이렇게 교체한다.

const [index, setIndex] = useState(0);

index는 상태 변수이고 setIndex는 상태 설정 함수이다.

[``] 는 배열 구조분해라는 문법이고 배열에서 값을 읽을 수 있게 해준다. useState에 의해 반환된 배열은 항상 두 개의 항목을 반환한다.

이것이 handleClick과 함께 작동되는 방법이다.

function handleClick() {
  setIndex(index + 1);
}

이제 "다음"버튼을 눌리면 이미지가 바뀔 것이다.

Meet your first Hook

use로 시작하는 다른 함수들처럼 useState도 훅이라고 불린다.

훅은 리액트가 렌더링하는 동안에만 사용할 수 있는 특별한 함수이다. 다양한 리액트 기능을 "연결(hooks into)"할 수 있게 해준다.

상태는 그 기능 중 하나이며 다른 기능들도 만나게 될 것이다.

🕳️pitfall
use로 시작하는 함수인 훅은 컴포넌트나 커스텀 훅의 최상단에서 호출되어야만 한다. 조건문이나 반복문, 중첩된 함수 안에서 훅을 호출할 수 없다. 훅은 함수이지만 컴포넌트의 필요에 대한 무조건적인 선언이라고 생각하면 유용하다. 파일의 맨 위에 있는 모듈을 "import"해오는 방법과 유사하게 컴포넌트 맨 위에서 기능을 사용한다.

Anatomy of useState

useState를 호출하면 이 컴포넌트가 다음 사항을 기억하기를 원한다고 알려준다.

const [index, setIndex] = useState(0);

이 케이스에서 index를 기억하도록 할 수 있다.

씽의 이름을 짓는 것은 관습적으로 const [something, setSomething]라고 한다. 원하는 이름으로 붙일 수 있지만 컨벤션은 프로젝트를 알아보기 쉽게 만든다.

useState의 단일 인자는 상태 변수의 초깃값이다. useState(0)으로 index의 초깃값을 0으로 지정할 수 있다.

컴포넌트가 렌더링 될 때마다 useState는 두 값이 있는 배열을 반환할 것이다.

  1. 저장한 값이 있는 상태 변수(index)
  2. 상태 변수를 업데이트하고 컴포넌트를 다시 렌더링하기 위해 리액트를 트리거하는 상태 변화 함수(setIndex)

동작이 일어나는 방법은 아래와 같다 :

const [index, setIndex] = useState(0);
  1. 컴포넌트는 처음으로 렌더링한다. useState0index의 초깃값으로 전달했기 때문에 [0, setIndex]를 반환할 것이다. 리액트는 0이 최신 상태 값임을 기억한다.
    2. 상태를 업데이트 한다. 사용자가 버튼을 클릭하면 setIndex(index+1)을 호출한다. index0이고, setIndex(1)이 된다. 이것은 리액트가 index1이라는 것을 기억하고 다른 렌더를 트리거한다.
  2. 컴포넌트는 두번째 렌더링을 한다. 리액트는 여전히 useState(0)이라고 알고있지만, index1이라고 설정한 것을 기억하므로 대신 [1, setIndex]를 반환한다.

Giving a component multiple state variables

한 컴포넌트에 원하는만큼 많은 변수를 가질 수 있다. 이 컴포넌트는 숫자값 index와 불린값 showMore의 두 개의 상태 변수를 가지고 "Show details"를 클릭하면 토글된다.

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}

예시에서의 indexshowMore과 같이 상태가 서로 관련이 없다면 여러개의 변수를 설정하는 것도 좋은 방법이다. 하지만 종종 두 변수가 함께 변하는 것을 발견한다면 두개를 하나로 묶는 것이 쉬울 수 있다. 예를 들어, 많은 필드로 이루어진 폼이 있다면 필드당 변수를 만드는 것보다 객체 하나를 관리하는 것이 편리하다. 이 곳에 더 많은 팁이 있으니 읽어보자.

🏊🏻‍♀️ DEEP DIVE

How does React know which state to return?

useState 호출이 상태 변수가 참조하는 어떠한 정보도 받지 않는다는 것을 눈치챘을지도 모른다. useState로 전달되는 어떠한 "식별자"도 없는데 어떤 상태 변수를 반환해야 하는지 어떻게 알 수 있을까?

간결한 구문을 사용하기 위해 Hooks는 매 렌더링마다 안정적인 호출 순서를 사용한다. 위의 규칙(최상위에서만 hooks를 호출)을 따르면 hooks는 항상 같은 순서로 호출되기 때문에 실제로 잘 작동한다. 또한 linter 플러그인은 대부분의 실수를 잡는다.

내부적으로 리액트는 모든 컴포넌트에 대한 상태 쌍 배열을 보유한다. 또한 렌더링 전에 0으로 설정된 현재 쌍 인덱스도 유지한다. useState를 호출할 때마다 리액트는 다음 상태 쌍을 제공하고 인덱스를 증가시킨다. 이 메커니즘에 대한 자세한 내용은 React Hooks: Not Magic, Just Arrays에서 확인할 수 있다.

State is isolated and private

상태는 화면의 컴포넌트 인스턴트에 로컬이다. 즉, 같은 컴포넌트를 두 번 렌더링하면 각 복사본은 완전히 격리된 상태가 된다. 둘 중 하나를 변경해도 다른 하나에는 영향을 주지 않는다.

이 예시에서 이 전의 Gallery 컴포넌트가 로직을 변경하지 않고도 두 번 렌더링 된다. 각 Gallery 안에 있는 버튼을 클릭해보면 각 상태가 독립적이라는 것을 알 수 있다.

//@App.js
import Gallery from './Gallery.js';

export default function Page() {
  return (
    <div className="Page">
      <Gallery />
      <Gallery />
    </div>
  );
}

//@Gallery.js
import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <section>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </section>
  );
}

이것이 상태를 모듈의 맨 위에 선언할 수 있는 일반 변수와 다르게 만드는 것이다. 상태는 특별한 함수 호출이나 코드의 특정 위치에 연결되지 않지만 화면의 특정 위치에 "로컬"된다. 두 개의 Gallery컴포넌트를 렌더링했으므로 해당 컴포넌트의 상태가 별도로 저장된다.

또한 페이지 컴포넌트가 Gallery 상태 또난 상태여부에 대해 "아는" 방법이 없다. props와 달리 상태는 이를 선언하는 컴포넌트에 대해 완전히 비공개이다. 부모 컴포넌트에서 변경시킬 수 없다. 이렇게 하면 나머지 컴포넌트에 영향을 주지 않고 컴포넌트에 상태를 추가하거나 제거할 수 있다.

만약 두 galleries가 상태를 일치시키기를 원한다면? 리액트에서의 올바른 방법은 자식 컴포넌트에서의 상태를 제거하고 가장 가까운 공유 부모 컴포넌트에 추가하는 것이다. 다음 몇 장은 단일 컴포넌트의 상태 구성에 대해 설명하지만 컴포넌트 간의 상태 공유라는 주제로 돌아갈 것이다.

RECAP

  • 컴포넌트가 렌더링 간에 일부 정보를 "기억"해야 하는 경우 상태 변수를 사용한다.
  • 상태 변수는 useState hook을 호출하여 선언된다.
  • hooks는 use로 시작하는 특별한 함수이고 상태와 같은 리액트 기능을 "연결" 할 수 있다.
  • hooks는 무조건 적인 호출인 import를 떠올리게 한다. hooks의 호출은 컴포넌트나 다른 훅 안의 최상위에서만 유효하다.
  • useState hook는 값의 쌍을 반환한다 : 현재 상태와 그것을 업데이트 할 함수
  • 하나 이상의 상태 변수를 가질 수 있다. 내부적으로 리액트는 그들의 순서대로 매치한다.
  • 상태는 컴포넌트에 비공개이다. 두 번 렌더링한다면 각 복사본은 각각의 상태를 가진다.
profile
기록하는 습관을 기르고 있습니다.

0개의 댓글