본문 바로가기
React

2021.08.08 React Context API

by 해맑은 코린이 2021. 8. 8.

2021.08.08 React Context API_정리노트

 

오느른~~~~ 전역상태를 관리하는 Context API 에 대한 정리노트! 리액트 v16.3버전부터 Context API 가 많이 개선되어 별도의 라이브러리 없이 전역 상태를 관리할 수 있다. 

 

전역 상태 관리?? 라는 것은 사용자 정보, 환경설정과 같은 모든 컴포넌트에 전역적으로 필요한 상태를 관리할 때를 말한다. 

지금까지 우리는 모든 컴포넌트에 대한 정보를 제공하는 최상위 컴포넌트인 App 에서 관리를 했는데 구조가 단순하다면 상관없지만,

 

만약 이런 경우라면 ?

 

출처 - 리액트를 다루는 기술

 

 

 

책에 있던 그림을 캡쳐해서 들고 왔다.. 조금 화질이 깨지지만 구조를 보면 이렇게 부모 자식간의 관계도를 보면, 

 

App 에서는 

 

const [value,setValue] = useState('doSomething');
const onSetValue = useCallback(value=> setValue(value), []);

 

이런식으로 useState 를 이용해서 상태를 업데이트 한다고 가정하자.

 

그러면 App 이 지니고 있는 value 값을 F, J 컴포넌트에 전달하려면, 여러 컴포넌트에 걸쳐서 전달해야한다.

 

F의 경우 App >> A >> B >> F

J인 경우 App >> H >> J

 

제일 하단에 있는 G 같은 경우에도 

 App >> A >> B >> E >> G

 

 

이렇게 복잡하게 여러번 거쳐 전달이 가능하다. 

 

이를 Context API 를 사용하면, Context 를 만들어 한번에 원하는 값을 받아 와서 사용할 수 있다. 

 

출처 - 리액트를 다루는 기술

 

 

 

이렇게!

 

 

그러면 실습으로 같이 정리 ㄱ

 

$ yarn create-react-app context-tutorial

 

 

 

 

context 라는 디렉터리를 만들고, ( 다른 파일과 구분이 쉽기 위해서. 상관은 없움 )

그 안에 color.js 를 만들었음.

 

 

 

src/context/color.js (새로 생성)

 

import {createContext}  from 'react';
import reactDom from 'react-dom';


//  createContext 로 새로운 함수 생성 . 파라미터에는 해당 Context 의 기본 상태 지정.
const ColorContext = createContext({color:'black'});


export default ColorContext;

 

새 Context를 만들 때는 createContext 함수를 사용한다. 파라미터는 state 랑 비슷하게 기본 상태를 넣어주면 된다.

 

 

이제 이를 받아오는 ColorBox 라는 컴포넌트를 components 디렉토리에 생성!

 

 

src/components/ColorBox.js  ( 새로 생성 )

 

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

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

export default ColorBox;

 

색상을 props 로 받아오는 것이 아니라 ColorContext 안에 들어 있는 Consumer 라는 컴포넌트를 통해 색상을 조회해보자!

 

먼가 형태가 컴포넌트 안에 함수라니 생소하다 .

이러한 패턴을 Function as a child  or Render Props 라고 한다.

이게 뭐냐라고 함은 

 

 

https://ko.reactjs.org/docs/render-props.html

 

Render Props – React

A JavaScript library for building user interfaces

ko.reactjs.org

출처 - 리액트 공식문서

 

 

컴포넌트의 children 이 있어야 할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 전달하는 것!!!!

 

출처 - 리액트 공식문서

 

공식문서에서도 이렇게 정의하고 있다. 

 

책에 있는 예시를 한 번 더 보면, 

 

 

import React from 'react';

const RenderPropsSample = ({children}) =>{

	return <div>결과 {chiledren(5)}</div>


}


export default RenderPropsSample;





// 사용할 때 



< RenderPropsSample > {value=> 2 * value }</ RenderPropsSample>

 

해당 패턴을 사용할 때, children props 로 파라미터에 2를 곱해서 반환하는 함수를 전달하면, 이 컴포넌트에서는 이 함수에 5를 인자로 넣어서 결과 10 을 렌더링하게 된다. 

 

출처 - 공식문서

 

 

그래서 우리는 render Props 패턴을 사용해서 Context.Consumer 이라는 것을 사용할 수 있는 것! 

 

Provider 에 대한건 바로 밑에서 다뤄보고 이까지 정리!

 

 

이제 ColorBox 컴포넌트를 App 에서 렌더링하면, 

 

src/App

 

 

 

이렇게  64px 의 검정 사각형이 뜨면 성공!

 

 

이제 그럼 Provider!

 

공식문서에서는 Provider 가 없으면 createContext 의 기본값을 전달한다고 했으니까, 이제는 Provider 를 사용해서 value를 변경해보자!

 

 

src/App

 

 

이렇게 빨간색으로 value 를 Provider 를 사용해서 바꿔주면,

 

 

짜잔 빨간색으로 변경한다. Provider를 사용하고 value 를 명시하지 않으면 기본값을 사용을 안하고 따로 value 를 바꿔준다고 했는데, 다른 설정값을 적어주지 않은 것이므로 오류를 띄운다. 

 

 

 

위의 예시들은 고정값을 사용하는 예이고 동적인 Context 를 사용해서 값을 업데이트 해줘야 하는 경우라면 ?

 

 

src/context/color

import { createContext, useState } from "react";
import reactDom from "react-dom";

// //  createContext 로 새로운 함수 생성 . 파라미터에는 해당 Context 의 기본 상태 지정.
// const ColorContext = createContext({color:'black'});

const ColorContext = createContext({
    // 기본값은 실제 Provider 에 넣는 value에 넣는 객체의 형태와 일치시켜주는 것이 좋음.
  state: { color: "black", subcolor: "red" },
  actions: {
    setColor: () => {},
    setSubcolor: () => {},
  },
});

const ColorProvider = ({ children }) => {
  const [color, setColor] = useState("black");
  const [subcolor, setSubcolor] = useState("red");
// 상태는 state, 업데이트 함수는 actions 로 묶어서 전달. 반드시 묶어줄 필요는 없지만, 나중에 Context 를 사용하기 편리함.
  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, ColorConnsumer 내보내기
export { ColorProvider, ColorConsumer };
export default ColorContext;

 

Context의 value 에는 고정적인 값뿐만 아니라 함수를 전달해줄 수도 있다. 

해당 컴포넌트에서는 ColorConntext.Provider 에 상태값과 업데이트하는 함수를 value 로 넣어주었다. 

 

오류를 막고, 파악하기 쉽게 상태값과 상태업데이트 함수를 묶어서 정리해서 넘겨주었다. 

 

그리고 ColorProvider, ColorConsumer 를 내보내서 각각 App 과 ColorBox 컴포넌트에 Provider,Consumer 자리에 대체해줄 것이다.

 

 

src/App

 

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

function App() {
  return (
    <>
      <ColorProvider>
        <ColorBox />
      </ColorProvider>
    </>
  );
}

export default App;

 

 

 

src/ColorBox

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

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

export default ColorBox;

 

 

이렇게!

 

 

또 비구조화 할당으로 value 를 생략해도 된다.

 

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

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

export default ColorBox;

 

 

 

두 개의 사각형이 잘 뜨면 성공!

 

이제는 업데이트 함수를 호출하는 컴포넌트를 만들어보자

 

 

src/components/SelectColors.js ( 새로 생성 )

import React from "react";

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

const SelectColors = () => {
  return (
    <div>
      <h2>select colors</h2>
      <div style={{ display: "flex" }}>
        {colors.map((color) => (
          <div
            key={color}
            style={{
              background: color,
              width: "24px",
              height: "24px",
              cursor: "pointer",
            }}
          />
        ))}
      </div>
    </div>
  );
};

export default SelectColors;

 

 

먼저 무지개 색깔 UI 를 만들고,

 

 

src/App

 

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

function App() {
  return (
    <>
      <ColorProvider>
        <SelectColors />
        <hr />
        <ColorBox />
      </ColorProvider>
    </>
  );
}

export default App;

App 에서 렌더링!

 

 

실행 화면

 

 

src/components/SelectColors

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

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

const SelectColors = () => {
  return (
    <div>
      <h2>select colors</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;

 

 

ColorConsumer 로 감싸서 render props 패턴으로 actions를 가져와서 각각 왼쪽 클릭을 했을 때는 큰 정사각형이 바뀌고, 오른쪽 클릭을 했을 때는 작은 사각형이 바뀌도록 한다. 

preventDefault 를 해주는 이유는 

 

 

이 메뉴가 뜨지 않게 하기 위해서! 

 

해당 이벤트는 오른쪽 클릭이벤트 적용시에 쓰면 된다. 보통 우클릭 방지를 위해 많이 쓴다.

 

 

 

실행화면

 

 

성공! 신기하다 클클

 


 

 

마지막으로 Consumer 대신에 Hook 을 사용해서 훨씬 간단하게 나타내보자.

 

 

createContext

render props 패턴이 익숙하지 않거나 불편하다면, 함수형 컴포넌트에서는 crateContext Hook 을 사용하여 편하게 Context 값을 조회할 수 있다. 

 

src/compoents/ColorBox

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

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

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

export default ColorBox;

 

와 함수를 전달 안해도 되니까 훨씬 보기가 좋구먼 껄껄

 

 

 


 

그동안 부모자식.. 자식의 자식.... 때문에 골치가 아팠었는데 물론 Context API 도 제대로 써봐야 완전히 자유자재로 쓰겠지만, 계층간에서 전달하고 전달하는게 헷갈렸던 만큼 오늘은 뭔가 와.. 하면서 같이 따라치며 정리했던 것 같다. 그리고 앞으로 배울 리덕스라는 라이브러리에서도 얘를 쓴다고 하니 활용 방법 또한 무궁무진 하겠군! 굿굿 오늘은 기쁘게 포스팅을 완료할 수 있겠다 그럼 끝!

 

 

 

댓글