Skip to main content

Command Palette

Search for a command to run...

[React]전역 상태관리란?<useContext를 이용한 전역 상태관리>

Updated
3 min read

특정 변수나 배열, 객체 등 어떤 자료구조든지 웹 페이지에서 계속해서 업데이트되면서 동적인 상호작용을 가능캐 하는 것은 전부 상태관리를 통해 이루어진다. React의 상태관리의 단초는 useState이다. State들은 props를 통해 위아래로 연결되며 서로 다른 파일에 있더라도 특정한 상태에 대한 제어를 할 수 있게 된다.

하지만 페이지의 규모가 커짐에 따라 컴포넌트 트리(컴포넌트 간 위계를 도식화한 것)는 더욱더 복잡하고 커지게 된다. 운용해야 할 상태 역시 많아질 수 있는데, 한 컴포넌트에서 다른 컴포넌트로 전달하는 props도 빼곡해지고, 경우에 따라 중간에서 쓰이지 않는 상태가 자식에서 쓰인다는 이유로 여러 컴포넌트를 불필요하게 경유하게 된다. 몇 단계만 걸쳐 내려가도 이 문제로 골치를 겪은 적이 한번씩은 있을 것이다. 이야말로 '상태관리를 위한 상태관리' 아닌가!

이러한 문제점에서 해결책으로 등장한 개념인 '전역 상태 관리'는, state를 props로 전달하는 방식이 아니라, 특정 컴포넌트 트리 전반에서 공유할 수 있게 한다.

전역 상태관리를 지원하는 api에는, react에 내장된 useContext 훅을 포함하여 redux, recoil, zustand, zotai, react-query 등 다양한 서드파티 라이브러리들까지 존재한다.

이 포스트에는 useContext를 활용하여 간단한 상태관리를 구현해보고자 한다.


구현은 크게 2단계로 이루어진다.

  1. context 만들기

  2. 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를 받아와 다음과 같이 간단히 사용가능하다.

문제점

💡
article의 업데이트는 어떻게 할까?

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;

참고자료

React 공식문서 - createContext

React 공식문서 - useContext

edited by 정정환

More from this blog

[ZSH] tree 사용하기

들어가며 큰 규모의 프로젝트를 출시한 뒤, 후일을 위해서 더 늦기 전에 파일 정리 및 문서화를 진행해야했다. 문서화 작업을 하는 중에 기왕 정리하는 거 파일 구조를 이쁘게 트리 구조로 나열하여 코멘트를 달면 나중에 보더라도 이해하기 더 쉬울 것 같았다. 어떻게 해야 간지나는 트리 구조를 만들 수 있을까 방법을 찾다보니 역시나 파일 구조를 트리로 이쁘게 출력해주는 커맨드 툴이 존재했다. tree 커맨드에 대해서 알아보고 알짜배기 내용만 정리했다....

Feb 21, 20242 min read

[Next.js] parallel routes & intercepting routes

트위터 로그인 모달창을 만들어보며 넥스트의 parallel routes 와 intercepting routes 을 학습한 내용을 정리해보았습니다. 트위터 로그인 창을 확인해봅시다. 루트 디렉토리 화면을 배경으로 i/flow/login 페이지가 동시에 표시되고 있습니다. 저는 app router 를 학습하기 전까지는 createPortal 을 사용하여 포탈 영역에 로그인 컴포넌트를 띄우는 방식을 사용했었습니다. const NoLogin =()=...

Feb 1, 20244 min read

C/C++ 이진 트리(binary tree) 개요 및 구현(1)

개요 트리는 노드들이 나무 가지처럼 연결된 비선형 계층적 자료구조이다. 하위 트리가 존재하고, 그 노드에 또 하위 트리가 존재하는 자료구조 이다. 트리의 맨 위에 있는 루트 노드가 존재한다. 우리가 알아볼 트리는 이진 트리이다. 이진 트리는 자식 노드(부모로부터 아래로 이어진 노드)가 2개 이하인 구조를 말한다. 트리의 사용 사례로는 다음과 같다 계층 적 데이터 저장(파일,폴더) 효율적인 검색 속도 힙 데이터 베이스의 인덱싱 트리에 ...

Jan 31, 20244 min read

[React] Server component (RSC)

React.js 18 에 도입된 리액트 서버 컴포넌트는 서버에서 동작하는 리액트 컴포넌트를 의미합니다. Next가 권장하는 라우팅 방식인 app router의 기반이 되는 컴포넌트이기 때문에 app router 를 이해하기 위해서는 server component 에 대한 이해가 필요합니다. server component 리액트는 클라이언트단만을 컴포넌트화하는 대신, server component라는 개념을 통해 서버 영역을 컴포넌트화합니다. ...

Jan 29, 20243 min read

Flutter, JavaScript

42 posts