[리액트공부] 15. Context API

kkado·2022년 8월 17일
0

리다기

목록 보기
16/16
post-thumbnail

Context API의 필요성

애플리케이션을 만들다 보면, 로그인 정보, 환경 설정 등 전역적으로 사용해야 하는 정보 등이 있을 것이다.

지금까지 배운 지식으로는 여러 컴포넌트 간에 사용하는 데이터가 있을 경우, 상위 컴포넌트에서부터 props 형태로 건네주는 방식으로 공유를 하고 있다. 하지만 프로젝트의 규모가 커지고, 컴포넌트가 겹겹이 싸여있는 경우 여러 컴포넌트를 거쳐서 내려가야 하므로 유지 보수성이 낮아질 가능성이 있다.

그렇기 때문에 리덕스나 MoBX 같은 상태 관리 라이브러리를 이용해 전역 상태 관리 작업을 더 편하게 처리하기도 하는데, 리액트 v16.3 업데이트 이후에는 Context API가 많이 개선되어서 별도의 라이브러리 없이도 전역 상태를 손쉽게 관리할 수 있다.

Context API 사용법

새 Context 만들기

새 context를 만들 때는 createContext 함수를 사용한다. 파라미터에는 해당 context의 기본 상태를 지정한다.

import { createContext } from "react";

const ColorContext = createContext({color: 'black'});

export default ColorContext;

Consumer 사용하기

import React from "react";
import ColorContext from "../contexts/color";

const ColorBox = () => {
  return (
    <ColorContext.Consumer>
      {(value) => (
        <div
          style={{
            width: "64px",
            height: "64px",
            background: value.color,
          }}
        />
      )}
    </ColorContext.Consumer>
  );
};
export default ColorBox;

사용할 context의 이름 뒤에 .Consumer 을 붙인 컴포넌트 안에 중괄호를 열어서 함수를 넣어 주었다.
이러한 패턴을 Function as a child 혹은 Render props 라고 한다. 컴포넌트의 children 자리에 JSX, 문자열이 아닌 함수를 전달하는 것이다.

context 값은 함수의 파라미터로 (위 코드에서는 value)로 사용할 수 있다.
위의 컴포넌트를 렌더링 해 보면 검은색 정사각형이 나타나는 것을 확인할 수 있다.

Provider

Provider는 context의 값을 변경할 수 있다. App.js를 다음과 같이 수정하자.

import ColorBox from "./components/ColorBox";
import ColorContext from "./contexts/color";

const App = () => {
  return (
    <ColorContext.Provider value={{color: 'red'}}>
      <div>
        <ColorBox />
      </div>
    </ColorContext.Provider>
  );
};

export default App;

context의 이름 뒤에 .Provider를 붙인 컴포넌트로 감싸준다.
그리고 value 속성을 수정해 줄 수 있다.

그 결과, 빨간색 정사각형이 나타나는 것을 볼 수 있다.

동적 Context 사용

Context 파일 수정

Context의 value 에는 무조건 상태 값만 있어야 하는 것은 아니고 함수를 전달해 줄 수도 있다.

import { createContext, useState } from "react";

const ColorContext = createContext({
  state: { color: "black", subcolor: "red" },
  actions: {
    setColor: () => {},
    setSubcolor: () => {},
  },
});

const ColorProvider = ({ children }) => {
  const [color, setColor] = useState("black");
  const [subcolor, setSubcolor] = useState("red");

  const value = {
    state: { color, subcolor },
    actions: { setColor, setSubcolor },
  };

  return (
    <ColorContext.Provider value={value}>{children}</ColorContext.Provider>
  );
};

const ColorConsumer = ColorContext.Consumer;
export { ColorProvider, ColorConsumer };

export default ColorContext;

위에서 ColorProvider라는 컴포넌트를 새로 작성해 주었고 그 컴포넌트에서는 ColorContext.Provider 을 렌더링하고 있다. 그리고 이 Provider의 value는 상태는 state로, 업데이트 함수는 actions로 묶어서 전달하고 있다.

createContext를 사용할 때 기본값으로 사용할 객체도 수정했다.
createContext의 기본값은 Provider의 value에 넣는 객체의 형태와 일치시켜주는 것이 좋다.

이제 App.jsColorContext.ProviderColorProvider로 교체한다.

import React from "react";
import ColorBox from "./components/ColorBox";
import { ColorProvider } from "./contexts/color";

const App = () => {
  return (
    <ColorProvider>
      <div>
        <ColorBox />
      </div>
    </ColorProvider>
  );
};

export default App;

마찬가지로 ColorBox.js에서 ColorContext.Consumer 역시 ColorConsumer로 교체하고, 밑에 작은 사각형을 하나 더 만든다.

import React from "react";
import {ColorConsumer} from '../contexts/color';

const ColorBox = () => {
  return (
    <ColorConsumer>
      {(value) => (
        <>
        <div
          style={{
            width: "64px",
            height: "64px",
            background: value.state.color,
          }}
        />
        <div
          style={{
            width: "32px",
            height: "32px",
            background: value.state.subcolor,
          }}
        />
        </>
      )}
    </ColorConsumer>
  );
};
export default ColorBox;

두 개의 정사각형이 렌더링되었다.

색상 선택 컴포넌트

이제 Context의 actions 함수를 호출하는 컴포넌트를 만들어 본다.
SelectColor.js

import React from "react";

const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];

const SelectColor = () => {
  return (
    <div>
      <h2>색상을 선택하세요.</h2>
      <div style={{ display: "flex" }}>
        {colors.map((color) => (
          <div
            key={color}
            style={{
              background: color,
              width: "24px",
              height: "24px",
              cursor: "pointer",
            }}
          />
        ))}
      </div>
      <hr />
    </div>
  );
};
export default SelectColor;

일곱 개의 색깔을 배열로 만들었고 map 함수를 이용해 하나씩 24px 크기의 정사각형을 만들었다. 그리고 이를 ColorBox 위에 렌더링해주면

위와 같은 정사각형들이 나타난다.
이제 SelectColor에서 마우스 왼쪽 버튼을 클릭하면 큰 정사각형의 색깔을, 마우스 오른쪽 버튼을 클릭하면 작은 정사각형의 색상을 변경하도록 구현해보자.

SelctColor.jsColorConsumer 내부의 코드를 다음과 같이 변경한다.

      <ColorConsumer>
        {({ actions }) => (
          <div style={{ display: "flex" }}>
            {colors.map((color) => (
              <div
                key={color}
                style={{
                  background: color,
                  width: "24px",
                  height: "24px",
                  cursor: "pointer",
                }}
                onClick={() => actions.setColor(color)}
                onContextMenu={(e) => {
                  e.preventDefault();
                  actions.setSubcolor(color);
                }}
              />
            ))}
          </div>
        )}
      </ColorConsumer>

마우스 좌클릭은 onClick, 우클릭은 onContextMenu 이벤트로 설정할 수 있다. 이 때 우클릭을 할 경우 메뉴 창이 뜨므로 preventDefault() 을 통해 기본 기능을 꺼준다.

그러면 클릭함에 따라 색깔이 잘 바뀌는 것을 확인할 수 있다.

useContext 훅 사용하기

리액트에 내장되어 있는 useContext라는 훅을 사용하면 함수형 컴포넌트에서 Context를 아주 편하게 사용할 수 있다.
ColorBox.js 을 다음과 같이 수정한다.

import React, { useContext } from "react";
import ColorContext from "../contexts/color";

const ColorBox = () => {
  const { state } = useContext(ColorContext);
  return (
    <>
      <div
        style={{
          width: "64px",
          height: "64px",
          background: state.color,
        }}
      />
      <div
        style={{
          width: "32px",
          height: "32px",
          background: state.subcolor,
        }}
      />
    </>
  );
};
export default ColorBox;

우선 ColorConsumer를 사용하지 않아도 되고, value 인자 없이 바로 state에서 꺼내어 사용할 수 있다.

정리

기존에는 컴포넌트 간에 state를 공유해야 할 때 무조건 부모 -> 자식 순서로 props를 통해 건네주었다면, 이제는 Context API를 통해 쉽게 상태를 공유할 수 있게 되었다.

profile
베이비 게임 개발자

0개의 댓글