컴포넌트는 상호 작용의 결과로 화면의 내용을 변경해야 하는 경우가 많습니다. 양식에 입력하면 입력 필드가 업데이트되고, 이미지 캐러셀에서 ‘다음’을 클릭하면 표시되는 이미지가 변경되고, ‘구매’를 클릭하면 제품이 장바구니에 담기게 됩니다. 컴포넌트는 현재 입력 값, 현재 이미지, 장바구니 등을 “기억”해야 합니다. React에서는 이러한 종류의 컴포넌트별 메모리를 State라고 합니다.
다음은 조각 이미지를 렌더링하는 컴포넌트입니다. “Next” 버튼을 클릭하면 index 가 1에서 2로 변경되어 다음 조각을 보여줘야 합니다. 하지만, 이는 작동하지 않습니다.
// 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>
</>
);
}
// data.js
export const sculptureList = [{
name: 'Homenaje a la Neurocirugía',
artist: 'Marta Colvin Andrade',
description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.',
url: 'https://i.imgur.com/Mx7dA2Y.jpg',
alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.'
}, {
.
.
.
}];
handleClick 이벤트 핸들러는 지역 변수, index 를 업데이트합니다. 하지만 두가지 요인으로 인해 해당 변경 사항이 보이지 않게 됩니다.
- 지역 변수는 렌더링 간에 유지되지 않습니다. React가 이 컴포넌트를 두 번째로 렌더링할 때 처음부터 렌더링합니다. 즉, 지역 변수에 대한 변경 사항을 고려하지 않습니다.
- 지역 변수를 변경해도 렌더링이 트리거되지 않습니다. React는 새 데이터로 컴포넌트를 다시 렌더링해야 한다는 것을 인식하지 못합니다.
새 데이터로 컴포넌트를 업데이트하려면 다음 두 가지 작업이 수행되어야 합니다.
- 렌더링 간에 데이터를 유지합니다.
- React를 트리거하여 새 데이터로 컴포넌트를 다시 렌더링합니다.
Hook 은 useState 다음 두가지를 제공합니다.
- 렌더링 간에 데이터를 유지하는 상태 변수입니다.
- 변수를 업데이트하고 React를 트리거하여 컴포넌트를 다시 렌더링하는 상태 설정 함수입니다.
useState 상태 변수를 추가하려면 파일 상단의 React에서 import합니다.
import { useState } from 'react';
let index = 0;
const [index, setIndex] = useState(0);
index 는 상태 변수이며 setIndex 는 설정 함수입니다.
[ 와 ] 구문을 배열 구조 분해라고 하며 이를 사용하면 배열에서 값을 읽을 수 있습니다.
useState에서 반환된 배열에는 항상 두 개의 항목이 있습니다.
아래와 같이 handleClick을 작성하여 작동할 수 있습니다.
// App.js
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
function handleClick() {
setIndex(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>
</>
);
}
React에서는 useState와 같이 use로 시작하는 다른 함수들을 Hook이라고 부릅니다.
Hooks은 React가 렌더링되는 동안에만 사용되는 특별 함수들입니다. 이를 통해 다양한 React의 기능을 연결할 수 있습니다.
Hook(use로 사작하는 함수)는 컴포넌트의 최상위 수준이나 자체 Hook에서만 호출할 수 있습니다. 조건, 반복문 또는 기타 중첩 함수 내에서는 Hook을 호출할 수 없습니다. Hook은 함수이지만 컴포넌트의 요구 사항에 대한 무조건적인 선언으로 생각하는 것이 도움이 됩니다. 파일 상단에서 모듈을 import하는 방법과 유사하게 컴포넌트 상단에서 React 기능을 사용합니다,
useStateuseState 를 호출하면 이 컴포넌트가 무엇인가를 기억하도록 React에게 알리는 것입니다.
const [index, setIndex] = useState(0);
컨벤션은 const [something, setSomething] 쌍과 같이 네이밍합니다. 원하는 네이밍은 할수는 있지만 이는 프로젝트 전반적인 이해도를 떨어뜨립니다.
유일한 인수는 useState 상태 변수의 초기 값입니다. 이 예제에서는 index의 초기 값이 useState(0)으로 0 으로 설정되어 있습니다.
컴포넌트가 렌더링 될때 마다 useState는 두 가지 값이 포함된 배열을 제공합니다.
- 저장한 값을 포함한 상태 변수(
index)- 상태 변수를 업데이트하고 React의 재 렌더링을 트리거하는 상태 변경 함수(
setIndex)
예시에서는 다음과 깉습니다.
- 컴포넌트가 처음으로 렌더링됩니다.
useState에index의 초기값으로 0을 넘겨 졌기 때문에 이는[0, setIndex]를 반환합니다. React는 0을 최근 상태 변수 값으로 기억합니다.- state를 업데이트합니다. 버튼을 클릭하면
setIndex(index+1)을 호출하고index는 0이 었기 때문에setIndex(1)이 됩니다. 이는 React에 index는 현재 1임을 전달하고 또 다른 렌더링을 트리거합니다.- 컴포넌트가 두번째로 렌더링됩니다. React는 아직
useState(0)으로 보이지만 React가 index가 1로 설정된 것을 기억하기 때문에, 이는[1, setIndex]를 대신 반환합니다.
하나의 컴포넌트에 원하는 만큼 많은 유형의 상태 변수를 가질 수 있습니다. 이 컴포넌트에는 ”Show details”를 클릭하면 변경되는 숫자 index, 부울 showMore 두가지 상태 변수를 갖고 있습니다.
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 호출이 어떤 상태 변수를 지칭하는지에 대한 정보를 받지 못한다는 것을 알아차렸을 수도 있습니다. 상태는 사용하기 위해 전달되는 “식별자”가 없는데, 어떤 상태 변수를 반환해야 하는지 어떻게 알 수 있을까요? 함수를 구문 분석하는 것은 아닙니다.
대신, Hooks은 간결한 구문을 가능하게 하기 위해, 동일한 컴포넌트의 모든 렌더링에 대하여 안정적인 호출 순서에 의존합니다. 위의 규칙(컴포넌트의 최상위 수준에서만 호출)을 따르면, Hooks은 항상 같은 순서로 호출되기 때문에 실제로 잘 작동합니다.
내부적으로 React는 모든 컴포넌트에 대해 state 쌍의 배열을 유지합니다. 또한 렌더링하기 전에 0으로 설정된 현재 쌍 인덱스를 유지합니다. uesState를 부를 때마다 React는 다음 state 쌍을 제공하고 인덱스를 증가합니다.
let componentHooks = [];
let currentHookIndex = 0;
// How useState works inside React (simplified).
function useState(initialState) {
let pair = componentHooks[currentHookIndex];
if (pair) {
// This is not the first render,
// so the state pair already exists.
// Return it and prepare for next Hook call.
currentHookIndex++;
return pair;
}
// This is the first time we're rendering,
// so create a state pair and store it.
pair = [initialState, setState];
function setState(nextState) {
// When the user requests a state change,
// put the new value into the pair.
pair[0] = nextState;
updateDOM();
}
// Store the pair for future renders
// and prepare for the next Hook call.
componentHooks[currentHookIndex] = pair;
currentHookIndex++;
return pair;
}
function Gallery() {
// Each useState() call will get the next pair.
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureList[index];
// This example doesn't use React, so
// return an output object instead of JSX.
return {
onNextClick: handleNextClick,
onMoreClick: handleMoreClick,
header: `${sculpture.name} by ${sculpture.artist}`,
counter: `${index + 1} of ${sculptureList.length}`,
more: `${showMore ? 'Hide' : 'Show'} details`,
description: showMore ? sculpture.description : null,
imageSrc: sculpture.url,
imageAlt: sculpture.alt
};
}
function updateDOM() {
// Reset the current Hook index
// before rendering the component.
currentHookIndex = 0;
let output = Gallery();
// Update the DOM to match the output.
// This is the part React does for you.
nextButton.onclick = output.onNextClick;
header.textContent = output.header;
moreButton.onclick = output.onMoreClick;
moreButton.textContent = output.more;
image.src = output.imageSrc;
image.alt = output.imageAlt;
if (output.description !== null) {
description.textContent = output.description;
description.style.display = '';
} else {
description.style.display = 'none';
}
}
.
.
.
state는 화면의 컴포넌트 인스턴스에 대하여 지역적입니다. 즉, 동일한 컴포넌트를 두 번 렌더링하면 각 인스턴스는 완전히 격리된 state가 되고 둘 중 하나를 변경해도 다른 하나에는 영향을 미치지 않습니다.
이 예시에서는, 이전 Gallery 컴포넌트가 로직 변경 없이 두번 렌더링됩니다. 각 갤러리 내부의 버튼을 눌러도 각각의 state는 독립적임을 알 수 있습니다.
이것이 state가 모듈 상단에서 선언할 수 있는 일반 변수와의 차이점입니다. state는 특정 함수 호출이나 코드의 위치에 묶여 있지 않지만 화면의 특정 위치에 “지역적”입니다. 두 개의 <Gallery /> 컴포넌트를 렌더링했으므로 해당 상태가 별도로 저장됩니다.
또한, Page 컴포넌트가 Gallery 컴포넌트의 state에 대하여 또는 state의 존재 여부를 알지 못합니다. props와 달리 state는 컴포넌트가 이를 선언하는 것에 대하여 공개되지 않습니다. 상위 컴포넌트는 state를 변경할 수 없습니다. 이를 통해 나머지 컴포넌트에 영향을 주지 않고 state를 추가하거나 제거할 수 있습니다.
두 Gallery의 상태를 동기화 하려면 하위 컴포넌트에서 state를 제거하고 가장 가까운 공유 부모 컴포넌트에 추가하는 것입니다.