ReactNextCentral

State: 컴포넌트 기억 공간

Published on
이 글은 리액트 컴포넌트에서 상태(state)를 다루는 방법과 useState Hook을 활용하는 방법에 대한 기본적인 내용을 다룹니다.
Table of Contents

컴포넌트는 상호작용 결과로 화면에 표시되는 내용을 변경해야 하는 경우가 많습니다. 예를 들어, 폼에 입력하면 입력 필드가 업데이트되어야 하고, 이미지 캐로셀에서 "다음"을 클릭하면 표시되는 이미지가 변경되어야 하며, "구매"를 클릭하면 제품이 장바구니에 추가되어야 합니다. 컴포넌트는 이런 정보를 "기억"해야 합니다. 현재 입력 값, 현재 이미지, 장바구니와 같은 것들을 말이죠. 리액트에서는 이러한 컴포넌트별 메모리를 상태(state)라고 합니다.

다음 내용을 배우게 됩니다.

  • useState Hook을 사용하여 상태 변수를 추가하는 방법
  • useState Hook이 반환하는 값의 쌍
  • 여러 개의 상태 변수를 추가하는 방법
  • 상태가 왜 로컬(local)이라고 불리는지

일반 변수만으로는 부족할 때

다음과 같이 조각 이미지를 렌더링하는 컴포넌트가 있습니다. "다음" 버튼을 클릭하면 인덱스가 1, 2 등으로 변경되어 다음 조각이 표시되어야 합니다. 그러나 이 방식은 작동하지 않습니다(시도해 볼 수 있습니다!):

Edit compassionate-chaplygin-tejhuy

handleClick 이벤트 핸들러는 로컬 변수인 index를 업데이트합니다. 그러나 변경 사항이 보이지 않게 하는 두 가지 이유가 있습니다.

  1. 로컬 변수는 렌더링 간에 지속되지 않습니다. 리액트는 이 컴포넌트를 두 번째로 렌더링할 때 완전히 처음부터 렌더링하므로 로컬 변수의 변경 사항을 고려하지 않습니다.
  2. 로컬 변수의 변경 사항은 렌더링을 트리거하지 않습니다. 리액트는 새로운 데이터로 컴포넌트를 다시 렌더링해야 함을 인지하지 못합니다.

새로운 데이터로 컴포넌트를 업데이트하려면 두 가지 작업이 필요합니다.

  1. 데이터를 유지합니다(렌더링 간에).
  2. 새 데이터로 컴포넌트를 다시 렌더링합니다.

이러한 두 가지를 제공하는 것이 useState Hook입니다.

  1. 상태 변수를 유지하는 데 사용됩니다.
  2. 상태 설정 함수를 제공하여 상태 변수를 업데이트하고 리액트에 컴포넌트를 다시 렌더링하도록 합니다.

상태 변수 추가

먼저 파일 상단에서 리액트에서 useState를 가져오세요.

import { useState } from 'react';

그런 다음 이 줄:

let index = 0;

다음과 같이 대체하세요.

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

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

여기서 사용된 [] 구문은 배열 비구조화라고 하며, 배열에서 값을 읽는 데 사용됩니다. useState에서 반환되는 배열은 항상 두 개의 항목이 있습니다.

다음은 handleClick에서 이들이 함께 작동하는 방식입니다.

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

이제 "다음" 버튼을 클릭하면 현재 조각이 전환됩니다.

Edit upbeat-keldysh-0efc0f

첫 번째 Hook 만나보기

리액트에서 useState와 같이 "use"로 시작하는 함수를 포함하여 다른 기능을 사용할 때는 Hook이라고 부릅니다.

Hook은 리액트가 렌더링하는 동안에만 사용할 수 있는 특별한 함수입니다(다음 페이지에서 자세히 알아보겠습니다). 이를 통해 다양한 리액트 기능에 "연결"할 수 있습니다.

상태는 이러한 기능 중 하나일 뿐이지만, 나중에 다른 Hook들을 배우게 될 것입니다.

주의사항

Hook은 use로 시작하는 함수로써 컴포넌트의 최상위 수준이나 자체적인 Hook 내에서만 호출할 수 있습니다. 조건문, 반복문 또는 다른 중첩된 함수 내에서는 Hook을 호출할 수 없습니다. Hook은 함수이지만 컴포넌트의 필요에 대한 무조건적인 선언으로 생각하는 것이 도움이 됩니다. 파일의 최상단에서 모듈을 가져오는 방식과 유사하게, 컴포넌트의 최상단에서 리액트 기능을 "사용"합니다.

useState의 구조

useState를 호출하면 이 컴포넌트가 어떤 정보를 기억하길 원하는지 리액트에 알리는 것입니다.

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

이 경우 리액트에 index를 기억하길 원한다고 알립니다.

자세히 알아보기: state 작성 방식인 const [something, setSomething]

관례적으로 이러한 쌍을 const [something, setSomething]과 같이 지정합니다. 이름을 원하는 대로 지정할 수 있지만, 관례를 따르면 다른 프로젝트에서 이해하기 쉽습니다.

useState의 유일한 인수는 상태 변수의 초기 값입니다. 이 예제에서 index의 초기 값은 useState(0)으로 설정됩니다.

컴포넌트가 렌더링될 때마다 useState는 다음과 같은 두 개의 값을 포함하는 배열을 제공합니다.

  1. 현재 상태 값을 가진 상태 변수(index)
  2. 상태 변수를 업데이트하고 리액트에 컴포넌트를 다시 렌더링하도록 하는 상태 설정 함수(setIndex)

다음은 실제로 동작하는 방식입니다.

const [index, setIndex] = useState(0);
  1. 컴포넌트가 처음 렌더링됩니다. index의 초기 값으로 0을 useState에 전달했으므로, [0, setIndex]를 반환합니다. 리액트는 0이 최신 상태 값이라는 것을 기억합니다.
  2. 상태를 업데이트합니다. 사용자가 버튼을 클릭하면 setIndex(index + 1)를 호출합니다. index는 0이므로 setIndex(1)로 설정됩니다. 이는 리액트에 index가 이제 1이라는 것을 기억하고 다시 렌더링을 트리거합니다.
  3. 컴포넌트가 두 번째로 렌더링됩니다. 리액트는 여전히 useState(0)을 보지만, index가 1로 설정된 것을 기억하고 [1, setIndex]를 반환합니다.
  4. 그리고 이어집니다!

컴포넌트에 여러 개의 상태 변수 제공

한 컴포넌트에 여러 개의 상태 변수와 여러 가지 타입의 상태 변수를 가질 수 있습니다. 다음 컴포넌트는 숫자 index와 "자세히 보기" 버튼을 클릭할 때 토글되는 불리언 showMore의 두 가지 상태 변수를 가지고 있습니다.

Edit agitated-burnell-jcnbsj

index와 showMore와 같이 상태가 관련 없을 경우에는 여러 개의 상태 변수를 가지는 것이 좋은 아이디어입니다. 그러나 두 상태 변수를 함께 자주 변경한다는 것을 알게 된다면, 두 변수를 하나로 결합하는 것이 더 편리할 수 있습니다. 예를 들어, 여러 필드가 있는 폼인 경우, 필드마다 상태 변수를 갖는 것보다 객체를 하나의 상태 변수로 가지는 것이 더 편리합니다. 더 많은 팁은 상태 구조 선택하기에서 읽어볼 수 있습니다.

자세히 알아보기: 리액트는 어떻게 상태를 반환할까요?

useState 호출에는 어떤 상태 변수를 참조하는지에 대한 정보가 전달되지 않는다는 사실을 알 수 있습니다. useState에 전달되는 "식별자"가 없으므로 상태 변수 중 어떤 것을 반환할지를 어떻게 알까요? 이를 위해 함수를 구문 분석하는 것과 같은 마법같은 방법을 사용할까요? 답은 아닙니다.

대신, 간결한 구문을 위해 Hook은 동일한 컴포넌트의 모든 렌더링에서 안정된 호출 순서에 의존합니다. 이는 실제로는 잘 작동하는데, 위의 규칙("Hook은 최상위 수준에서만 호출하세요")을 따른다면 Hook은 항상 동일한 순서로 호출됩니다. 또한 린터 플러그인이 대부분의 실수를 잡아줍니다.

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

이 예제는 리액트를 사용하지 않지만, useState가 내부적으로 어떻게 작동하는지에 대한 개념을 이해하는 데 도움이 될 수 있습니다.

리액트를 사용하기 위해 이해할 필요는 없지만, 이것은 유용한 개념 모델일 수 있습니다.

상태는 격리되고 비공개입니다.

상태는 화면의 컴포넌트 인스턴스에 대해 로컬입니다. 다른 말로 하면, 동일한 컴포넌트를 두 번 렌더링하면 각각이 완전히 격리된 상태를 가집니다! 그 중 하나를 변경해도 다른 하나에는 영향을 미치지 않습니다.

이 예제에서는 이전에 보여준 갤러리 컴포넌트가 로직에 변경 없이 두 번 렌더링됩니다. 각 갤러리 안의 버튼을 클릭해 보세요. 그들의 상태가 독립적임을 알 수 있습니다.

Edit divine-currying-rodp3h

이것이 상태를 모듈의 맨 위에 선언하는 일반 변수와 다른 점입니다. 상태는 특정 함수 호출이나 코드의 위치에 연결되지 않지만, 화면의 특정 위치에 "로컬"되어 있습니다. 여러분은 두 개의 <Gallery /> 컴포넌트를 렌더링했으므로 그들의 상태는 별도로 저장됩니다.

또한 Page 컴포넌트가 갤러리 상태를 "알지" 않거나 상태가 있는지 여부를 모릅니다. props와 달리 상태는 선언한 컴포넌트에 완전히 비공개입니다. 부모 컴포넌트는 상태를 변경할 수 없습니다. 이렇게 하면 다른 컴포넌트에 영향을 주지 않고 어떤 컴포넌트에든 상태를 추가하거나 제거할 수 있습니다.

만약 두 개의 갤러리가 상태를 동기화하여 유지하려면 리액트에서는 자식 컴포넌트에서 상태를 제거하고 가장 가까운 공유 부모에 상태를 추가하는 것이 옳은 방법입니다. 다음 몇 페이지에서는 단일 컴포넌트의 상태를 조직화하는 방법에 초점을 맞출 것이지만, 컴포넌트 간에 상태 공유하기 주제로 돌아올 것입니다.

요약

  • 컴포넌트가 렌더링 간에 일부 정보를 "기억"해야 할 때 상태 변수를 사용합니다.
  • 상태 변수는 useState Hook을 호출하여 선언합니다.
  • Hook은 "use"로 시작하는 특별한 함수입니다. 이를 통해 상태와 같은 리액트 기능에 "연결"할 수 있습니다.
  • Hook은 import와 비슷한 방식으로 호출되어야 합니다. useState를 포함한 Hook 호출은 컴포넌트의 최상위 수준이나 다른 Hook 내에서만 유효합니다.
  • useState Hook은 현재 상태와 상태를 업데이트할 수 있는 함수의 쌍을 반환합니다.
  • 여러 개의 상태 변수를 가질 수 있습니다. 리액트 내부적으로 순서를 맞춥니다.
  • 상태는 컴포넌트에 대해 비공개입니다. 동일한 컴포넌트를 여러 곳에서 렌더링하면 각각의 사본에는 독립적인 상태가 있습니다.