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