[React]전역 상태관리란?<useContext를 이용한 전역 상태관리>
특정 변수나 배열, 객체 등 어떤 자료구조든지 웹 페이지에서 계속해서 업데이트되면서 동적인 상호작용을 가능캐 하는 것은 전부 상태관리를 통해 이루어진다. React의 상태관리의 단초는 useState이다. State들은 props를 통해 위아래로 연결되며 서로 다른 파일에 있더라도 특정한 상태에 대한 제어를 할 수 있게 된다.
하지만 페이지의 규모가 커짐에 따라 컴포넌트 트리(컴포넌트 간 위계를 도식화한 것)는 더욱더 복잡하고 커지게 된다. 운용해야 할 상태 역시 많아질 수 있는데, 한 컴포넌트에서 다른 컴포넌트로 전달하는 props도 빼곡해지고, 경우에 따라 중간에서 쓰이지 않는 상태가 자식에서 쓰인다는 이유로 여러 컴포넌트를 불필요하게 경유하게 된다. 몇 단계만 걸쳐 내려가도 이 문제로 골치를 겪은 적이 한번씩은 있을 것이다. 이야말로 '상태관리를 위한 상태관리' 아닌가!
이러한 문제점에서 해결책으로 등장한 개념인 '전역 상태 관리'는, state를 props로 전달하는 방식이 아니라, 특정 컴포넌트 트리 전반에서 공유할 수 있게 한다.
전역 상태관리를 지원하는 api에는, react에 내장된 useContext 훅을 포함하여 redux, recoil, zustand, zotai, react-query 등 다양한 서드파티 라이브러리들까지 존재한다.
이 포스트에는 useContext를 활용하여 간단한 상태관리를 구현해보고자 한다.
구현은 크게 2단계로 이루어진다.
context 만들기
context 사용하기
Context 만들기
일반적으로 context를 정의하는 부분은 파일을 따로 만들어서 관리한다.
여기 article-context.js에서 우리가 관리할 자료형을 정의하고, context를 구현해보자.
type Article = { //for typescript
id: string;
time: number;
};
const dummyArticleList: Article[] = [
{
id: "article1",
time: 130,
},
{
id: "article2",
time: 40,
},
{
id: "article3",
time: 75,
}
];
Article이라는 객체는 id와 time 필드를 가지며, dummy list는 리스트의 초기값이다.
우리는 Article의 배열을 관리하고자 한다.
export const articleContext = createContext();
createContext는 context객체를 생성한다.
context객체 자체는 정보를 가지고 있지 않고, 어떤 state에 대한 context인지를 나타낸다.
다른 파일에서 이 articleContext를 사용해야 하니 export 키워드를 달아주자.
export const ArticleProvider = () => {
const [articleState, setArticleState] = useState(dummyArticleList);
return (
<articleContext.Provider value={articleState}>
{children}
</articleContext.Provider>
);
};
articleState는 상태관리의 대상이므로 useState에 등록시켜준다.
ArticleProvider란, 하나의 wrapper로서 이 context를 사용하는 다른 컴포넌트를 감싸기 위한 컴포넌트다.
context객체가 가지고 있는 context.Provider는, 감싸고 있는 children이 그 깊이에 상관없이 context에 접근할 수 있게 한다. 그 원형은
<articleContext.Provider value={initialValue}>
<Page />
<articleContext.Provider>
과 같은데, context를 선언하는 파일을 분리시키면서 wrapper 컴포넌트를 리턴하게 한다.
- useState는 함수 밖에서 호출이 안되므로 component 안에서 선언시켜준다.
article-context.js 전문
//article-context.js
import { createContext, useState } from "react";
export type Article = { //for typescript
id: string;
elapsedTime: number;
};
const dummyArticleList: Article[] = [
{
id: "article1",
elapsedTime: 130,
},
{
id: "article2",
elapsedTime: 40,
},
{
id: "article3",
elapsedTime: 75,
},
];
export const articleContext = createContext();
export const ArticleProvider = () => {
const [articleState, setArticleState] = useState(dummyArticleList);
return (
<articleContext.Provider value={articleState}>
{children}
</articleContext.Provider>
);
};
Context 사용하기
//App.js
<ArticleProvider>
<Home />
</ArticleProvider>
우선 context가 필요한 최상단 루트를 Provider로 감싸준다.
import { useContext } from "react";
import { articleContext } from "@/context/article-context";
const Home = () => {
const articles = useContext(articleContext);
console.log(articles[0].id) //article1
...
}
context를 사용하고자 하는 컴포넌트에서는 미리 생성한 context를 import해야 한다.
useContext(articleContext)로 value를 받아와 다음과 같이 간단히 사용가능하다.
문제점
Home에서는 article를 업데이트하는 setArticle함수가 현재 없다.
Article를 업데이트하는 함수 역시 context를 통해 같이 전달해보도록 하자.
export const ArticleProvider = () => {
const [articleState, setArticleState] = useState(dummyArticleList);
const addArticle = (id) => {
const newArticle = { id: id, elapsedTime: 0 };
const newState = [...articleState, newArticle];
setArticleState(newState);
};
const removeArticle = (id) => {
const newState = articleState.filter((item)=>{
if(item.id != id) return true
});
setArticleState(newState);
};
return (
<articleContext.Provider value={{articles: articleState, addArticle: addArticle, removeArticle: removeArticle}}>
{children}
</articleContext.Provider>
);
};
addArticle, removeArticle 함수를 구현해 value에 동봉해주었다.
이는 이제 Home에서 다음과 같이 사용가능하다.
const ctx_article = useContext(articleContext);
const articles = ctx_article.articles;
const addArticle = ctx_article.addArticle;
const removeArticle = ctx_article.removeArticle;
참고자료
edited by 정정환