본문 바로가기

048. React - Context API 상태관리

codeConnection 2024. 5. 28.

Context란?

React의 상태를 관리하는 방법 중 하나.

나오게 된 이유는, 리액트에서 상태를 전달하는 특성 때문인데, 리액트에서 정의한 state는 부모->자식으로 전달이 가능하고 만약 부모->자식->자식 으로 손주까지 내려가려면 부모에서 바로 손주로 내려갈 수 없고 바로 위의 부모에 한 번 전달한 뒤 다시 그 바로 위 부모로부터 전달을 받아야 한다. 이런 현상을 props drilling이라 한다. 컴포넌트가 조금만 많아지더라도 매우 복잡하기 때문에 context가 생겼다.

context는 상태를 저장하는 보관소 느낌이다. 만들어 둔 상태들을 context에 저장해두고 필요한 곳에서 꺼내어 쓰면 되기 때문에 자식의 자식의 자식으로 타고타고 뚫고 들어가는 프롭스 드릴링을 하지 않아도 되어 편리하다.

기본 사용법

리액트 라이브러리로 제공하는 기능이기에 별도의 패키지 설치는 필요 없음

// state를 저장할 context를 만들기 위해 훅을 import
import { createContext, useState } from 'react';

// 작명은 무관하나 파스칼 케이스로 Context를 생성
// 보통 상태명을 단수로 하고 그 뒤에 Context로 붙임
// 핵심은 컴포넌트 밖에서 선언한다는 것임. 이유는 App 컴포넌트 등 그 안에서 선언하게 되면
// 그 컴포넌트가 리 렌더링 될 때마다 context가 계속 재생성됨. context의 역할은
// 하위 컴포넌트에 state 등을 전달해주기만 하면 되기 때문에 계속 생성할 필요가 없음.
const MemberContext = createContext();

// 아래에 컴포넌트를 정의해도 되고
// 이 과정을 별도의 js/jsx 파일로 빼서 관리해도 됨.
// Provider 컴포넌트를 정의하고 상태를 관리함.
const MemberProvider = ({ children }) => {
  const [members, setMembers] = useState(['장원영', '안유진', '이서']);

  const addMember = (name) => {
    setMembers([...members, name]);
  };

  return (
    {/* 이 context를 전달받을 곳에 context컴포넌트명.Provider로 value 키를 통해 객체로 묵어서
    필요한 것들을 props를 내려주면 됨. */}
    <MemberContext.Provider value={{ members, addMembers }}>
      {children}
    </MemberContext.Provider>
  );
};

export { MemberContext, MemberProvider };

context를 갖다 쓰는 입장에서는 컨텍스트 컴포넌트를 import 하고 컴포넌트를 호출할 때 사용할 곳을 Provider 컴포넌트로 감싸주면 됨.

// App.jsx
// 최상위 컴포넌트에서 Provider 컴포넌트를 사용할 곳을 감싸주기

import { MemberProvider } from './MemberContext';
function App() {
  return (
    <MemberProvider>
      <div>
        <h1>Member List</h1>
        <AddMember />
        <MemberList />
      </div>
    </MemberProvider>
  );
}

export default App;

이렇게 하면 AddMember 컴포넌트와 MemberList 컴포넌트는 Provider 컴포넌트로 감싸져 있기 때문에 MemberProvider에서 제공하는 데이터(state나 useEffect 등...)을 갖다 쓸 수 있음.

갖다 쓰는 방법은 아래와 같음.

// 하위 컴포넌트

// context를 import하고
import { MemberContext } from './MemberContext';

// 갖다 쓰고 싶은 함수, 변수, state를 함수형 컴포넌트 내부에서 구조 분해 할당으로 가져오면 됨.
// 그러면 이후 props를 내리지 않아도 자유롭게 사용 가능.
const { addMember } = useContext(MemberContext);

// 구조 분해 할당을 하지 않고 아래처럼 context 전체를 들고 와도 됨. 다 쓸 거면.
const memberList = useContext(MemberContext);

정리

  1. createContext import
import { createContext } from 'react';
  1. context 생성
// 함수형 컴포넌트 밖에서 생성해야 함.
const MyContext = createContext();
  1. context를 공유하고 싶은 컴포넌트들을 context의 Provider 프로퍼티를 컴포넌트로 감싸기
return(
    <>
        <MyContext>
         <Input/>
        </MyContext>
    </>
)
  1. 공유하고 싶은 state, 함수 등을 value로 내려주기

통째로 내려주거나 구조 분해 할당 해서 내려주거나.

return(
    <>
        <MyContext.Provider value={ { member, setMember } }>
         <Input/>
        </MyContext.Provider>
    </>
)
  1. context export 하기
// 이미 export 중인 별도의 컴포넌트에서 export 하려면 중괄호로 묶어서.
export { App, MyContext } 
  1. 하위 컴포넌트에서 갖다 쓰기
// 갖다 쓰려는 컴포넌트에서 Context(보관함)을 import
import { myContext } from './App.jsx';

// Context를 사용하겠다는 코드 작성
useContext(myContext);

// 보통은 변수에 담아서 활용
let member = useContext();

// 그런데 저 context에 많은 데이터가 담겨 있다면 구조 분해 할당으로 필요한 것만 꺼내오면 됨.
let { memberName } = useContext();

context API의 단점

현업에서는 Redux라는 상태 관리 라이브러리를 더 많이 사용한다. 이유는 context API가 성능 관리 측면에서 단점이 있기 때문.

context.Provider로 감싼 컴포넌트들은 이 컨텍스트에 있는 값이 변경되면 모조리 리 렌더링이 발생함. 실제로 감싸져 있는 컴포넌트들 중에서 Provider에 포함된 모든 데이터를 다 사용하진 않을 텐데 내가 직접적으로 사용하지 않는 값이 변경되더라도 감싸져 있다면 모두 리 렌더링이 됨.

return(
    <>
        <MyContext.Provider value={{member, age}}>
            <Input/>
            <List/>
        </MyContext.Provider>
    </>
)

예를 들어서 위에서 Input 컴포넌트는 member라는 state를 갖다 쓰고, List에서 age라는 컴포넌트를 갖다 쓴다고 했을 때 Input 컴포넌트는 age state를 사용하지 않음에도 age state의 값이 변경되면 Input 컴포넌트, List 컴포넌트 모두 리 렌더링이 발생하는 비효율이 있다.

댓글