[React] 동적인 Context API 사용 원코드 정리

<dev Yoo />·2022년 11월 20일
0

[React]

목록 보기
2/2
  • 리액트에서 Context API를 동적으로 사용하는 3가지 방법을 한 코드 속에 정리해보았다. 커스텀 Provider를 통해 정적으로 Context를 사용하는 것이 아닌, 하위 컴포넌트에서 직접 값 변경이 가능하도록 코드를 짜보았다.
    • 2022.11. 현재 React v18.2.0에 맞게 제작
  • bgColor, borderColor theme을 설정하는 예시이다. (아래 GIF 참고)

  • 참고 문헌
    • 김민준.(2021).리액트를 다루는 기술, 개정판.서울:도서출판 길벗
    • 리액트 공식 문서: https://ko.reactjs.org/

ThemeContext.tsx - Context 생성

import { createContext, useState } from "react";

// Context API 기본 + 동적으로 사용하기
//
// <사용 절차>
// 1. createContext를 통해 초기 value 설정
//      - 여기서의 초기 value는 Provider 없을 때 사용 됨
// 2. 원하는 사용 범위를 MyContext.Provider + value를 통해 감싸기 (Provider 없어도 사용은 가능)
//      - 동적인 Context 사용시 Provider 컴포넌트를 커스텀으로 제작
// 3. Context value 실제 사용 방법
//      1) Consumer 사용 -> 함수 children(render props 패턴)을 통해
//      2) useContext 사용 -> FC에서만
//      3) static contextType 사용 -> Class 컴포넌트에서만, this.context가 자동으로 value로 할당 됨

export interface ITheme {
  state: { bgColor: string; borderColor: string };
  actions: {
    setBgColor: React.Dispatch<React.SetStateAction<string>>;
    setBorderColor: React.Dispatch<React.SetStateAction<string>>;
  };
}

interface IProps {
  children: React.ReactNode;
}

// createContext(initialValue)
//  - 초기 value는 객체일 필요 X, 아래 예시는 동적 활용을 위해 제작
//  - 동적 활용을 할 때에는 state와 actions로 나누어 주는 것이 사용에 용이
const ThemeContext = createContext<ITheme>({
  state: {
    bgColor: "#ffffff",
    borderColor: "#000000",
  },
  actions: {
    setBgColor: () => {},
    setBorderColor: () => {},
  },
});

// 동적 Context 사용을 위한 커스텀 Provider
// actions 함수의 호출마다 state가 변경되어 Provider의 value가 변경되도록 설계
const ThemeContextProvider = ({ children }: IProps) => {
  const [bgColor, setBgColor] = useState("#ffffff");
  const [borderColor, setBorderColor] = useState("#000000");

  const value: ITheme = {
    state: { bgColor, borderColor },
    actions: { setBgColor, setBorderColor },
  };

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

const { Consumer: ThemeContextConsumer } = ThemeContext;

export { ThemeContextProvider, ThemeContextConsumer };

export default ThemeContext;

ContextTheme.tsx - class component에서 Context의 사용 예시

import React from "react";
import { ThemeContextConsumer } from "../../shared/components/ThemeContext";
import ContextThemeUsingConsumer from "./ContextThemeUsingConsumer";
import ContextThemeUsingStatic from "./ContextThemeUsingStatic";

// Context의 value 구독 및 하위 컴포넌트 변경 예시
//  - 해당 코드는 useContext를 제외한 나머지 두 방법을 다룸
//      1) Consumer 사용
//      2) static contextType 사용

class ContextTheme extends React.Component {
  render(): React.ReactNode {
    return (
      <div>
        <h1>Context Theme w/ Class Component</h1>

        <div style={{ display: "flex", gap: "30px" }}>
          {/* 방법 1. Consumer 사용 */}
          <ThemeContextConsumer>
            {(value) => <ContextThemeUsingConsumer {...value} />}
          </ThemeContextConsumer>

          {/* 방법 2. static contextType 사용 */}
          <ContextThemeUsingStatic />
        </div>
      </div>
    );
  }
}

export default ContextTheme;

ContextThemeUsingConsumer.tsx - Consumer를 이용한 Context 사용

import React from "react";
import { ITheme } from "../../shared/components/ThemeContext";

interface IProps extends ITheme {}

// Consumer를 이용한 Context 사용
//  - 부모 컴포넌트에서 Consumer의 render props를 통해 props로 context value를 전달 받음

class ContextThemeUsingConsumer extends React.Component<IProps> {
  handleChange = (
    e: React.ChangeEvent<HTMLInputElement>,
    setColor: React.Dispatch<React.SetStateAction<string>>
  ) => {
    setColor(e.target.value);
  };

  render(): React.ReactNode {
    const { state, actions } = this.props;

    return (
      <div>
        <h2>#1. Using Consumer</h2>
        <div
          style={{
            width: "100px",
            height: "100px",
            backgroundColor: state.bgColor,
            border: "5px solid",
            borderColor: state.borderColor,
          }}
        />

        <label htmlFor="bgColorChange">bgColor: </label>
        <input
          type="color"
          id="bgColorChange"
          value={state.bgColor}
          onChange={(e) => this.handleChange(e, actions.setBgColor)}
        />

        <label htmlFor="borderColorChange">borderColor: </label>
        <input
          type="color"
          id="borderColorChange"
          value={state.borderColor}
          onChange={(e) => this.handleChange(e, actions.setBorderColor)}
        />
      </div>
    );
  }
}

export default ContextThemeUsingConsumer;

ContextThemeUsingStatic.tsx - static contextType을 이용한 Context 사용

import React from "react";
import ThemeContext, { ITheme } from "../../shared/components/ThemeContext";

// static contextType을 이용한 Context 사용
//  - static contextType을 Context 자체로 설정하면 this.context가 value로 할당 됨

class ContextThemeUsingStatic extends React.Component {
  static contextType?: React.Context<ITheme> | undefined = ThemeContext;
  context!: React.ContextType<typeof ThemeContext>;

  handleChange = (
    e: React.ChangeEvent<HTMLInputElement>,
    setColor: React.Dispatch<React.SetStateAction<string>>
  ) => {
    setColor(e.target.value);
  };

  render(): React.ReactNode {
    const { state, actions } = this.context;

    return (
      <div>
        <h2>#2. Using static contextType</h2>
        <div
          style={{
            width: "100px",
            height: "100px",
            backgroundColor: state.bgColor,
            border: "5px solid",
            borderColor: state.borderColor,
          }}
        />

        <label htmlFor="bgColorChange">bgColor: </label>
        <input
          type="color"
          id="bgColorChange"
          value={state.bgColor}
          onChange={(e) => this.handleChange(e, actions.setBgColor)}
        />

        <label htmlFor="borderColorChange">borderColor: </label>
        <input
          type="color"
          id="borderColorChange"
          value={state.borderColor}
          onChange={(e) => this.handleChange(e, actions.setBorderColor)}
        />
      </div>
    );
  }
}

export default ContextThemeUsingStatic;

UseContextTheme.tsx - useContext 훅을 이용한 Functional component에서의 사용법

import { useContext } from "react";
import ThemeContext from "../../shared/components/ThemeContext";

// useContext: Context API를 Consumer 없이 사용 가능
//  - Consumer 없이 사용 가능하지만, 동적 사용을 위해서는 Provider로 감싸줘야 함!
//  - 매개변수로 해당 Context 그 자체를 넘겨야 함

const UseContextTheme = () => {
  const { state, actions } = useContext(ThemeContext);

  const handleChange = (
    e: React.ChangeEvent<HTMLInputElement>,
    setColor: React.Dispatch<React.SetStateAction<string>>
  ) => {
    setColor(e.target.value);
  };

  return (
    <div>
      <h2>useContext Theme</h2>
      <div
        style={{
          width: "100px",
          height: "100px",
          backgroundColor: state.bgColor,
          border: "5px solid",
          borderColor: state.borderColor,
        }}
      />

      <label htmlFor="bgColorChange">bgColor: </label>
      <input
        type="color"
        id="bgColorChange"
        value={state.bgColor}
        onChange={(e) => handleChange(e, actions.setBgColor)}
      />

      <label htmlFor="borderColorChange">borderColor: </label>
      <input
        type="color"
        id="borderColorChange"
        value={state.borderColor}
        onChange={(e) => handleChange(e, actions.setBorderColor)}
      />
    </div>
  );
};

export default UseContextTheme;
profile
기획하는 개발자, 디자인하는 엔지니어

0개의 댓글