Study/React

15. Context API

delay100 2022. 7. 30. 21:37
728x90
반응형
SMALL

안녕하세요! delay100입니다. 이번 포스팅에서는 Context API에 대해 공부해봅시다.

대부분의 설명은 주석으로 달아놓았으니 코드에 대한 설명은 주석을 확인해주세요! 

 리액트를 다루는 기술, 개정판 15 내용을 다루고 있습니다.

프로젝트 실행 방법에 대한 설명은 여기에 있습니다.

이번 포스팅의 Github 링크

https://github.com/delay-100/study-react/tree/main/ch15/context-tutorial
 

GitHub - delay-100/study-react

Contribute to delay-100/study-react development by creating an account on GitHub.

github.com


1. Context API?

 

기존에는 컴포넌트 간에 상태 교류 시 무조건 부모에서 자식으로 props를 통해 전달해주었습니다. 이제는 Context API를 통해 더욱 쉽게 상태를 교류할 수 있습니다.

 

Context API란?

- 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을 때 유용한 기능(ex. 사용자 로그인 정보, 애플리케이션 환경 설정, 테마 등)
- 리덕스, 리액트 라우터, styled-components 등의 라이브러리가 Context API를 기반으로 구현 되어있음
- 전역적으로 여기저기서 사용되는 상태가 있고 컴포넌트의 개수가 많은 상황에 사용하면 좋음
- 프로젝트 컴포넌트 구조가 꽤 간단하고 다루는 상태의 종류가 많지 않다면 굳이 Context를 사용할 필요는 없음

 


2. Context API 사용하기

 


2-1. 시작하기

 

src 디렉터리 안에 contexts 디렉터리를 생성한 뒤 아래의 파일(color.js)을 만들어줍시다.

Context 사용 시 디렉터리의 이름 등은 contexts말고 다른 경로를 해도 됩니다. 여기서는 다른 파일들과 구분하기 위해 만들어주었습니다.

  • Context API 기본 사용 예제  - src/contexts/color.js
import { createContext } from "react";

const ColorContext = createContext({ color: "black" }); // 새 Context를 만들 때는 createContext 함수를 사용. 파라미터에는 Context의 기본 상태를 지정

export default ColorContext;

이어서 components 디렉터리도 생성후 아래의 파일을 만들어주었습니다.

  • Context API 기본 사용 예제  - src/components/ColorBox.js
// ColorBox 컴포넌트: ColorContext 안에 들어 있는 색상을 보여주는 컴포넌트
import ColorContext from "../contexts/color";

const ColorBox = () => {
  return (
    <ColorContext.Consumer>
      {/* Function as a child(Render Props): Consumer 사이에 중괄호를 열어서 그 안에 함수를 넣어 주는 것
      컴포넌트의 child가 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달하는 것*/}

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

export default ColorBox;
  • Context API 기본 사용 예제  - src/App.js
import ColorBox from "./components/ColorBox";
import ColorContext from "./contexts/color";

const App = () => {
  return (
    <ColorContext.Provider value={{ color: "red" }}>
      {/*Provider 사용 시 Context의 value를 바꿀 수 있음. Provider을 사용한다면 value를 꼭 넣어줘야 함 -> 미기재시 에러 */}
      <div>
        <ColorBox />
      </div>
    </ColorContext.Provider>
  );
};

export default App;

Context API 기본 사용 예제 실행 결과

 


2-2. 동적 Context

 

2-1에서는 고정적인 값만 사용할 수 있었습니다. 이번에는 Context의 값을 업데이트해야 하는 경우를 다뤄봅시다.

 

  • 동적 Context 사용 예제  - src/contexts/dynamiccolor.js
import { createContext, useState } from "react";

const ColorContext = createContext({
  // createContext의 기본값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋음
  state: { color: "black", subcolor: "red" },
  actions: {
    setColor: () => {},
    setSubcolor: () => {},
  },
}); // 새 Context를 만들 때는 createContext 함수를 사용. Context의 value에는 상태 값 뿐만아니라 함수를 전달할 수도 있음

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

  const value = {
    // Provider의 value에는 상태는 state, 업데이트 함수는 actions로 묶어서 전달, 반드시 묶어줄 필요는 없지만 state와 actions 객체를 따로 분리해주면 나중에 다른 컴포넌트에서 Context 값 사용시 편리함
    state: { color, subcolor },
    actions: { setColor, setSubcolor },
  };
  return (
    <ColorContext.Provider value={value}>{children}</ColorContext.Provider>
  );
};

// const ColorConsumer = ColorContext.Consumer 와 같은 의미
const { Consumer: ColorConsumer } = ColorContext;

// ColorProvider와 ColorConsumer 내보내기
export { ColorProvider, ColorConsumer };

export default ColorContext;
  • 동적 Context 사용 예제  - src/components/DynamicColorBox.js
// DynamicColorBox 컴포넌트: ColorContext 안에 들어 있는 색상을 보여주는 컴포넌트
import { ColorConsumer } from "../contexts/dynamiccolor";

const DynamicColorBox = () => {
  return (
    <ColorConsumer>
      {/* Function as a child(Render Props): Consumer 사이에 중괄호를 열어서 그 안에 함수를 넣어 주는 것
      컴포넌트의 child가 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달하는 것*/}

      {(
        { state } // 비구조화 할당 사용 시 value 생략 가능
      ) => (
        // {(value) => (
        <>
          <div
            style={{
              width: "64px",
              height: "64px",
              background: state.color,
              // background: value.state.color,
            }}
          />

          <div
            style={{
              width: "32px",
              height: "32px",
              background: state.subcolor,
              // background: value.state.subcolor,
            }}
          />
        </>
      )}
    </ColorConsumer>
  );
};

export default DynamicColorBox;
  • 동적 Context 사용 예제  - src/App.js
import DynamicColorBox from "./components/DynamicColorBox";
import { ColorProvider } from "./contexts/dynamiccolor";

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

export default App;

동적 Context 사용 예제 실행 결과

 


2-3. 색상 선택 컴포넌트 만들기

 

이번에는 Context의 actions에 넣어 준 함수를 호출하는 컴포넌트를 만들어봅시다.

  • 색상 선택 컴포넌트 예제  - src/components/SelectColors.js
import { ColorConsumer } from "../contexts/dynamicolor";

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

const SelectColors = () => {
  return (
    <div>
      <h2>색상을 선택하세요.</h2>
      <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>
    </div>
  );
};

export default SelectColors;
  • 색상 선택 컴포넌트 예제  - src/contexts/dynamiccolor.js
import { createContext, useState } from "react";

const ColorContext = createContext({
  // createContext의 기본값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋음
  state: { color: "black", subcolor: "red" },
  actions: {
    setColor: () => {},
    setSubcolor: () => {},
  },
}); // 새 Context를 만들 때는 createContext 함수를 사용. Context의 value에는 상태 값 뿐만아니라 함수를 전달할 수도 있음

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

  const value = {
    // Provider의 value에는 상태는 state, 업데이트 함수는 actions로 묶어서 전달, 반드시 묶어줄 필요는 없지만 state와 actions 객체를 따로 분리해주면 나중에 다른 컴포넌트에서 Context 값 사용시 편리함
    state: { color, subcolor },
    actions: { setColor, setSubcolor },
  };
  return (
    <ColorContext.Provider value={value}>{children}</ColorContext.Provider>
  );
};

// const ColorConsumer = ColorContext.Consumer 와 같은 의미
const { Consumer: ColorConsumer } = ColorContext;

// ColorProvider와 ColorConsumer 내보내기
export { ColorProvider, ColorConsumer };

export default ColorContext;
  • 색상 선택 컴포넌트 예제  - src/App.js
import DynamicColorBox from "./components/DynamicColorBox";
import { ColorProvider } from "./contexts/dynamiccolor";
import SelectColors from "./components/SelectColors";

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

export default App;

App.js에 쓰인 DynamicColorBox 코드는 동적 Context 사용 예제  - src/components/DynamicColorBox.js 와 동일합니다.

 

색상 선택 컴포넌트 예제 실행 결과

  • 마우스 왼쪽 버튼 클릭: 큰 정사각형의 색상 변경
  • 마우스 오른쪽 버튼 클릭: 작은 정사각형의 색상 변경

 


이 아래 부터는 Context에 있는 값을 사용할 때 Consumer 대신 다른 방식을 사용하여 값을 받아오는 방법을 알아봅시다.

 

3. 함수 컴포넌트 - Hook 이용

 

리액트의 내장 Hooks 중에서 useContext라는 Hook 사용 시 함수 컴포넌트에서 Context를 아주 편하게 사용할 수 있습니다. 앞서 children에 함수를 전달하는 Render Props 패턴을 썼는데, Hook을 사용하면 더 쉽게 Context 값을 조회할 수 있습니다.

이 방식은 함수 컴포넌트에서만 사용 가능하고, 클래스형 컴포넌트에서는 사용이 불가합니다. 

 

  • useContext Hook 사용 예src/components/HookColorBox.js
// HookColorBox 컴포넌트: ColorContext 안에 들어 있는 색상을 보여주는 컴포넌트
import { useContext } from "react";
import ColorContext from "../contexts/dynamiccolor";

const HookColorBox = () => {
  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 HookColorBox;

2-3 코드 중에서 DynamicColorBox.js만 위의 코드로 변경한다면, 2-3과 같은 실행 결과를 볼 수 있습니다.


4. 클래스형 컴포넌트 - static contextType 이용

 

이번에는 클래스형 컴포넌트에서 Context를 더 쉽게 사용하는 방법을 알아봅시다. 

static contextType을 정의하는 방법입니다.

  • 장점: 클래스 메서드에서도 Context에 넣어 둔 함수를 호출할 수 있음
  • 단점: 한 클래스에서 하나의 Context밖에 사용하지 못함

 

  • static contextType 사용 예 src/components/ClassSelectColors.js
import { Component } from "react";
import ColorContext from "../contexts/color";

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

class SelectColors extends Component {
  static contextType = ColorContext; // this.context를 조회했을 때 현재 Context의 value를 가리키게 됨
  // 만약 setColor를 호출하고 싶다면 this.context.actions.setColor을 호출하면 됨

  handleSetColor = (color) => {
    this.context.actions.setColor(color);
  };
  handleSetSubcolor = (subcolor) => {
    this.context.actions.setSubcolor(subcolor);
  };
  render() {
    return (
      <div>
        <h2>색상을 선택하세요.</h2>
        {({ actions }) => (
          <div style={{ display: "flex" }}>
            {colors.map((color) => (
              <div
                key={color}
                style={{
                  background: color,
                  width: "24px",
                  height: "24px",
                  cursor: "pointer",
                }}
                onClick={() => this.handleSetColor(color)}
                onContextMenu={(e) => {
                  e.preventDefault(); // 마우스 오른쪽 버튼 클릭 시 메뉴가 뜨는 것을 무시함
                  this.handleSetSubColor(color);
                }}
              />
            ))}
          </div>
        )}
      </div>
    );
  }
}

export default SelectColors;

2-3 코드 중에서 SelectColors.js만 위의 코드로 변경한다면, 2-3과 같은 실행 결과를 볼 수 있습니다.

그러나, 앞으로 새로운 컴포넌트를 작성 시 클래스형으로 작성하는 일은 거의 없기 때문에 useContext를 사용하는 것이 좋습니다.

 


읽어주셔서 감사합니다. 잘못된 정보는 댓글로 알려주세요! 

728x90
반응형
LIST