# State 끌어올리기
State 끌어올리기는 React에서 자주 사용되는 패턴 중 하나다. 두 개 이상의 컴포넌트가 동일한 데이터를 공유하거나 각 컴포넌트의 상태가 연관 되어 있을 때 개별적으로 state를 관리하는 대신 가장 가까운 공통 부모 컴포넌트로 state를 이동시켜 말 그대로 자식 state를 부모 쪽에서 관리할 수 있게 끌어올리는 것을 말한다.
왜 이런 패턴이 필요할까? 예를 들어 아코디언 UI에서 자식 패널의 상태를 공유해서 하나의 패널만 열린 상태를 가지고 있어야 한다면 각 패널이 개별적으로 자신의 열림/닫힘 상태를 관리해서는 안된다. 서로 다른 컴포넌트의 상태를 동기화하여 컨트롤하려면 공통의 관리자가 필요하다.
# 제어 컴포넌트 vs 비제어 컴포넌트
State 끌어올리기를 이해하면서 중요한 개념이 제어 컴포넌트와 비제어 컴포넌트의 구분이다.
내가 알고 있던 제어/비제어 컴포넌트는 리액트에게 컴포넌트의 제어권을 위임하고 state로 관리하거나 ref을 사용하여 DOM을 직접 관리하는 방식이지만, State 끌어올리기에서 설명하는 제어/비제어 컴포넌트는 개념이 달라 글을 읽었을 때 약간 혼동이 생있었다. 그래서 제어/비제어 컴포넌트가 아닌 제어/비제어 패턴으로 바꿔 얘기해 보겠다.
비제어 패턴
컴포넌트가 자체 local state를 가지고 있어서 부모가 직접적으로 제어할 수 없는 상태
제어 패턴
컴포넌트의 정보가 props로부터 전달되어 부모 컴포넌트가 완전히 하위 컴포넌트를 제어할 수 있는 상태
// 제어 패턴
// props로 isActive를 받아 부모가 제어하는 형태
const ControlledPanel = ({title, children, isActive, onClick}) => {
return (
<section>
<h3>{title}</h3>
{/* Props로 제어 */}
{isActive ? (
<p>{children}</p>
) : (
<button onClick={onClick}>show</button>
)}
</section>
)
}
// 비제어 패턴
// local state로 직접 제어
const UnControlledPanel = ({title, children}) => {
const [isActive, setIsActive] = useState(false);
return (
<section>
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={()=> setIsActive(prev => !prev)}>
Show
</button>
)}
</section>
)
}
제어 패턴은 유연성이 높지만 부모에서 더 많은 설정이 필요하고 비제어 패턴은 사용하기 쉽지만 여러 컴포넌트강 동기화를 구현하기 어렵다. State 끌어올리기를 통해 비제어 컴포넌트를 제어 컴포넌트로 변경시키는 것이 핵심이다.
# 단일 진리의 원천 (Single Source of Truth)
State 끌어올리기의 핵심 원칙은 단일 진리의 원천이다. 이는 동일한 데이터에 대해 여러 곳에서 중복으로 관리하지 않고 하나의 컴포넌트에서만 관리한다는 의미이다.
예를 들어, 사용자가 입력한 검색어를 여러 컴포넌트에서 사용해야 한다면
// SearchBox에서 사용하는 query 상태는 SearchResults에서도 동일한 상태여야한다.
const SearchBox = () => {
const [query, setQuery] = useState('');
// ....
}
const SearchResults = () => {
const [query, setQuery] = useState('');
// ....
}
// 중복된 state 사용을 state 끌어올리기를 활용하여
// 불필요한 state를 줄이고 데이터 동기화를 해준다.
const SearchPage = () => {
const [query, setQuery] = useState('');
return (
<>
<SearchBox query={query} onQueryChange={setQuery}/>
<SearchResults query={query}/>
</>
)
}
이렇게 동기화가 필요한 state를 끌어올리면 데이터 일관성을 보장할 수 있고 state 동기화 문제도 해결할 수 있다.
실제 프로젝트에서의 활용
실제로 이 패턴은 다양한 상황에서 사용된다.
•탭 인터페이스: 현재 활성화된 탭을 부모에서 관리
•폼 관리: 여러 입력 필드의 값을 한 곳에서 관리
•모달 상태: 여러 모달 중 어떤 모달이 열려있는지 부모에서 관리
# 마치며
State 끌어올리기는 리액트에서 컴포넌트 간 state를 공유하는 가장 간단한 방법이고 자주 쓰이는 패턴이다. 이 패턴은 단방향 데이터 흐름이라는 리액트의 철학을 잘 보여주는 패턴이기도 한거 같다고 생각한다.
의식하지 않았지만 다들 프로젝트를 진행하다보면 state 동기화가 필요한 순간이 생기고 State 끌어올리기 패턴을 자주 사용하고 있을 것이다. 이 패턴 외에도 리액트에서 제공하는 Context API나 전역 상태관리 라이브러리를 사용해 Props drilling을 피하고 컴포넌트 간 state를 공유할 수 있지만 기본기인 state 끌어올리기를 확실히 이해하는 것이 무엇보다 중요하다고 생각한다.