컴포넌트는 상호작용의 결과로 화면을 변경시켜야 할 때가 자주 있다. 폼에 입력하는 것은 인풋 영역을 업데이트 해야하고, 이미지 슬라이드의 "다음" 버튼을 클릭하면 보여지는 화면이 바뀌어야 하고, "구매하기" 를 누르면 쇼핑카트에 상품을 넣을 수 있어야 한다. 컴포넌트는 현재 입력 값, 이미지, 쇼핑카트를 "기억할" 필요가 있다. 리액트에서 이런 컴포넌트별 메모리를 상태(state)라고 한다.
배울 것
useState훅을 사용하여 상태 변수를 등록하는 방법useState훅의 반환값으로 나오는 값은 어떤 쌍인지- 하나 이상의 상태 변수를 더하는 방법
- 상태가 로컬에서 호출되어야 하는 이유
sculpture 이미지를 렌더링하는 컴포넌트가 있다. "다음" 버튼을 누르면 index를 1에서 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. 변수를 업데이트하고 컴포넌트를 다시 렌더링하게 하는 상태 설정 함수.
상태 변수를 추가하기 위해 useState를 파일 최상단에서 import한다.
import { useState } from 'react';
그리고 아래 줄을
let index = 0;
이렇게 교체한다.
const [index, setIndex] = useState(0);
index는 상태 변수이고 setIndex는 상태 설정 함수이다.
[``]는 배열 구조분해라는 문법이고 배열에서 값을 읽을 수 있게 해준다.useState에 의해 반환된 배열은 항상 두 개의 항목을 반환한다.
이것이 handleClick과 함께 작동되는 방법이다.
function handleClick() {
setIndex(index + 1);
}
이제 "다음"버튼을 눌리면 이미지가 바뀔 것이다.
use로 시작하는 다른 함수들처럼 useState도 훅이라고 불린다.
훅은 리액트가 렌더링하는 동안에만 사용할 수 있는 특별한 함수이다. 다양한 리액트 기능을 "연결(hooks into)"할 수 있게 해준다.
상태는 그 기능 중 하나이며 다른 기능들도 만나게 될 것이다.
🕳️pitfall
use로 시작하는 함수인 훅은 컴포넌트나 커스텀 훅의 최상단에서 호출되어야만 한다. 조건문이나 반복문, 중첩된 함수 안에서 훅을 호출할 수 없다. 훅은 함수이지만 컴포넌트의 필요에 대한 무조건적인 선언이라고 생각하면 유용하다. 파일의 맨 위에 있는 모듈을 "import"해오는 방법과 유사하게 컴포넌트 맨 위에서 기능을 사용한다.
useState를 호출하면 이 컴포넌트가 다음 사항을 기억하기를 원한다고 알려준다.
const [index, setIndex] = useState(0);
이 케이스에서 index를 기억하도록 할 수 있다.
씽의 이름을 짓는 것은 관습적으로
const [something, setSomething]라고 한다. 원하는 이름으로 붙일 수 있지만 컨벤션은 프로젝트를 알아보기 쉽게 만든다.
useState의 단일 인자는 상태 변수의 초깃값이다. useState(0)으로 index의 초깃값을 0으로 지정할 수 있다.
컴포넌트가 렌더링 될 때마다 useState는 두 값이 있는 배열을 반환할 것이다.
index)setIndex)동작이 일어나는 방법은 아래와 같다 :
const [index, setIndex] = useState(0);
useState에 0을 index의 초깃값으로 전달했기 때문에 [0, setIndex]를 반환할 것이다. 리액트는 0이 최신 상태 값임을 기억한다.setIndex(index+1)을 호출한다. index는 0이고, setIndex(1)이 된다. 이것은 리액트가 index가 1이라는 것을 기억하고 다른 렌더를 트리거한다.useState(0)이라고 알고있지만, index를 1이라고 설정한 것을 기억하므로 대신 [1, setIndex]를 반환한다.한 컴포넌트에 원하는만큼 많은 변수를 가질 수 있다. 이 컴포넌트는 숫자값 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}
/>
</>
);
}
예시에서의 index와 showMore과 같이 상태가 서로 관련이 없다면 여러개의 변수를 설정하는 것도 좋은 방법이다. 하지만 종종 두 변수가 함께 변하는 것을 발견한다면 두개를 하나로 묶는 것이 쉬울 수 있다. 예를 들어, 많은 필드로 이루어진 폼이 있다면 필드당 변수를 만드는 것보다 객체 하나를 관리하는 것이 편리하다. 이 곳에 더 많은 팁이 있으니 읽어보자.
useState 호출이 상태 변수가 참조하는 어떠한 정보도 받지 않는다는 것을 눈치챘을지도 모른다. useState로 전달되는 어떠한 "식별자"도 없는데 어떤 상태 변수를 반환해야 하는지 어떻게 알 수 있을까?
간결한 구문을 사용하기 위해 Hooks는 매 렌더링마다 안정적인 호출 순서를 사용한다. 위의 규칙(최상위에서만 hooks를 호출)을 따르면 hooks는 항상 같은 순서로 호출되기 때문에 실제로 잘 작동한다. 또한 linter 플러그인은 대부분의 실수를 잡는다.
내부적으로 리액트는 모든 컴포넌트에 대한 상태 쌍 배열을 보유한다. 또한 렌더링 전에 0으로 설정된 현재 쌍 인덱스도 유지한다. useState를 호출할 때마다 리액트는 다음 상태 쌍을 제공하고 인덱스를 증가시킨다. 이 메커니즘에 대한 자세한 내용은 React Hooks: Not Magic, Just Arrays에서 확인할 수 있다.
상태는 화면의 컴포넌트 인스턴트에 로컬이다. 즉, 같은 컴포넌트를 두 번 렌더링하면 각 복사본은 완전히 격리된 상태가 된다. 둘 중 하나를 변경해도 다른 하나에는 영향을 주지 않는다.
이 예시에서 이 전의 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가 상태를 일치시키기를 원한다면? 리액트에서의 올바른 방법은 자식 컴포넌트에서의 상태를 제거하고 가장 가까운 공유 부모 컴포넌트에 추가하는 것이다. 다음 몇 장은 단일 컴포넌트의 상태 구성에 대해 설명하지만 컴포넌트 간의 상태 공유라는 주제로 돌아갈 것이다.
useState hook을 호출하여 선언된다.use로 시작하는 특별한 함수이고 상태와 같은 리액트 기능을 "연결" 할 수 있다.useState hook는 값의 쌍을 반환한다 : 현재 상태와 그것을 업데이트 할 함수