관리 메뉴

거니의 velog

(2) Context API 2 본문

React/React_리액트 심화

(2) Context API 2

Unlimited00 2023. 12. 13. 10:35

3. 동적 Context 사용하기

* 지금까지 배운 내용으로는 고정적인 값만 사용할 수 있다. 이번에는 Context의 값을 업데이트해야 하는 경우 어떻게 해야 하는지 알아보자.


(1) Context 파일 수정하기

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

* 기존에 작성했던 ColorContext의 코드를 다음과 같이 수정해 보자. 이번에 코드를 작성한 후 저장하면 오류가 발생할 텐데, 해당 오류는 나중에 수정할 것이므로 걱정하지 말자.

[color.js]

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와 같은 의미
const { Consumer: ColorConsumer } = ColorContext;

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

export default ColorContext;

* 위 파일에서 ColorProvider라는 컴포넌트를 새로 작성해 주었다. 그리고 그 컴포넌트에서는 ColorContext.Provider를 렌더링하고 있다. 이 Provider와 value에는 상태는 state로, 업데이트 함수는 actions로 묶어서 전달하고 있다. Context에서 값을 동적으로 사용할 떄 반드시 묶어 줄 필요는 없지만, 이렇게 state와 actions 객체를 따로따로 분리해 주면 나중에 다른 컴포넌트에서 Context의 값을 사용할 때 편하다.

* 추가로 createContext를 사용할 때 기본값으로 사용할 객체도 수정했다. createContext의 기본값은 실제 Provider의 value에 넣는 객체의 형태와 일치시켜 주는 것이 좋다. 그렇게 하면 Context 코드를 볼 때 내부 값이 어떻게 구성되어 있는지 파악하기도 쉽고, 실수로 Provider를 사용하지 않았을 때 리액트 애플리케이션에서 에러가 발생하지 않는다.


(2) 새로워진 Context를 프로젝트에 반영하기

* 코드를 다 작성했으면 App 컴포넌트에서 ColorContext.Provider를 ColorProvider로 대체하자.

[App.js]

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도 마찬가지로 ColorContext.Consumer를 ColorConsumer로 변경하자. 또한, 사용할 value의 형태도 바뀌었으니 이에 따른 변화를 다음과 같이 반영시켜 보자.

[ColorBox.js]

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

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

export default ColorBox;

* 위 코드에서 객체 비구조화 할당 문법을 사용하면 다음과 같이 value를 조회하는 것을 생략할 수도 있다.

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

const ColorBox = () => {
  return (
    <ColorConsumer>
      {({ state }) => (
        <>
          <div
            style={{
              width: "64px",
              height: "64px",
              backgroundColor: state.color,
            }}
          />
          <div
            style={{
              width: "32px",
              height: "32px",
              backgroundColor: state.subColor,
            }}
          />
        </>
      )}
    </ColorConsumer>
  );
};

export default ColorBox;

* 코드를 다 작성했다면 브라우저를 확인해 보자.

여러 상태 값 사용하기

* 검정색 정사각형과 빨간색 정사각형이 잘 보이는가?


(3) 색상 선택 컴포넌트 만들기

* 이번에는 Context의 actions에 넣어 준 함수를 호출하는 컴포넌트를 만들어 보자. components 디렉터리에 SelectColors.js 라는 파일을 생성하여 다음 코드를 작성해 보자. 지금은 Consumer를 사용하지 않고 UI만 준비해 보자.

import React from "react";

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

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

export default SelectColors;

* 다 작성했으면 이 컴포넌트를 App 컴포넌트에서 ColorBox 위에 렌더링 하자.

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

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

export default App;

* 브라우저에 다음과 같이 무지개 색상으로 이루어진 정사각형이 나타나는가?

SelectColors UI

* 이제 해당 SelectColors 에서 마우스 왼쪽 버튼을 클릭하면 큰 정사각형의 색상을 변경하고, 마우스 오른쪽 버튼을 누르면 작은 정사각형의 색상을 변경하도록 구현해 보자.

[SelectColors.js]

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

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={{
                  backgroundColor: color,
                  width: "24px",
                  height: "24px",
                  cursor: "pointer",
                }}
                onClick={() => actions.setColor(color)}
                onContextMenu={(e) => {
                  e.preventDefault(); // 마우스 오른쪽 버튼 클릭 시 메뉴가 뜨는 것을 무시함
                  actions.setSubColor(color);
                }}
              />
            ))}
          </div>
        )}
      </ColorConsumer>
      <hr />
    </div>
  );
};

export default SelectColors;

* 마우스 오른쪽 버튼 클릭 이벤트는 onContextMenu를 사용하면 된다. 오른쪽 클릭 시 원래 브라우저 메뉴가 나타나지만, 여기서 e.preventDefault() 를 호출하면 메뉴가 뜨지 않는다.

* 브라우저를 열어서 SelectColors 에 있는 정사각형들을 마우스 왼쪽 및 오른쪽 버튼으로 클릭해 보자. 하단에 있는 정사각형들의 색상이 잘 바뀌면 된다.

색상 바꿔보기