리액트 - Context API 정리

노요셉·2019년 12월 20일
2

antd ( Ant Design )을 이용해 어드민 페이지를 구성하고 있습니다.

다양한 형태의 table이 필요한데, antd에서는 다양한 형태의 테이블이 있어서 적용하려는데
Context API 코드가 나와서 일단 정리하고 antd table-demo-edit-row를 사용하려고 합니다.

Context API란

Context API 왜 why?

G컴포넌트에서 상태를 업데이트 시키면 F,J 컴포넌트에서 업데이트된 상태를 렌더링 하는 구조면

일단 G컴포넌트 상태를 F,J로 올리는 방법은 제가 지금 당장은 모르겠습니다. ( 써드파티 라이브러리 도움 없이) 있을 수는 있지만 지금 구조에서는 Root에서 상태를 관리하고, G에게는 A->B->E를 거쳐서 Root의 상태를 업데이트할 메서드를 props로 전달할거에요. 그리고 G에서 상태를 업데이트하면 Root의 상태가 변경될것이고, F, J에게는 상태를 props로 전달할거에요. F의 경우는 Root->A->B를 통해서 전달했을 것이고, J의 경우 Root->H를 통해서 props로 상태를 전달했을 겁니다.

그래서 업데이트된 상태를 전달받은 F,J 컴포넌트는 상태가 변경될때마다 렌더링이 될것입니다.

복잡합니다. props로 상태를 전달하고, 상태를 업데이트할 메서드를 전달하고..

그래서 나온게 Context인 것 같아요.
redux, react-router, styled-components 등의 라이브러리는 Context API 기반으로 구현이 되었다네요.

새 Context 만들기

리액트 프로젝트를 생성합니다.

npx create-react-app context-tutorial
cd context-tutorial
code ./

src/contexts 디렉토리 생성해서
color.js 자바스크립트 파일을 생성해서 그 안에 Context를 생성합니다.

color.js

import { createContext } from 'react';

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

export default ColorContext;

Consumer 사용하기

/src/components 디렉토리 생성해서 ColorBox.js를 생성해서 다음과 같이 작성합니다.
Context에 color값을 여기서
<ColorContext.Consumer> 안에 value.color로 뽑아서 쓰고 있습니다.
마치 전역변수를 쓰는 것 처럼요.

src/components/ColorContext.js

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;

App.js에서 ColorBox 컴포넌트를 등록해두면 가로세로 64px div 영역이 잡힌것을 확인할 수 있습니다.

import React from 'react';
import ColorBox from './components/ColorBox';

function App() {
  return (
    <div className="App">
      <ColorBox />
    </div>
  );
}

export default App;

Provider

color.js에서 color를 전역 상태로 관리하고 있죠. 이제 전역상태를 수정하고싶을때 사용하는게 Provider입니다.

App.js

import React from 'react';
import ColorBox from './components/ColorBox';
import ColorContext from './contexts/color';

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

export default App;

기존에 createContext 함수를 사용할 때는 파라미터로 Context의 기본값을 넣어 주었지요?
만약 Provider는 사용했는데 value를 명시하지 않았다면, 이 기본 값을 사용하지 않기 때문에 오류가 발생합니다.

function App() {
  return (
    <ColorContext.Provider>
      <div className="App">
        <ColorBox />
      </div>
    </ColorContext.Provider>
  );
}

동적 Context 사용하기

Context에는 value말고도 함수를 전달할 수도 있습니다.

레퍼런스 : A quick intro to React’s props.children - Jason Arnold

color.js

import React, { 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 { Consumer: ColorConsumer } = ColorContext;

export { ColorProvider, ColorConsumer };

export default ColorContext;

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

ColorProvider에서 ColorContext.Provider를 담아서 리턴하기 때문에
기존 <ColorContext.Provider value={{ color: 'red' }}>코드를 ColorProvider로 대체합니다.

src/App.js

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

function App() {
  return (
    <ColorProvider>
      <div className="App">
        <ColorBox />
      </div>
    </ColorProvider>
  );
}

export default App;

ColorConsumer도 기존 <ColorContext.Consumer> 에서 비구조화 할당을 통해 ColorConsumer로 할당받았습니다.

src/components/ColorBox.js

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

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

export default ColorBox;

state도 비구조화 할당을 통해서 다음과 같이 쓸 수 있습니다.
src/components/ColorBox.js

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

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

export default ColorBox;

색상 선택 컴포넌트 만들기

빨주노초파남보 색을 배열에 담아서
각각을 div영역으로 표시하는 컴포넌트입니다.

src/components/SelectColor.js

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={{
              background: color,
              width: '24px',
              height: '24px',
              cursor: 'pointer',
            }}
          />
        ))}
      </div>
      <hr />
    </div>
  );
};

export default SelectColors;

App.js

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

function App() {
  return (
    <ColorProvider>
      <div className="App">
        <SelectColors />
        <ColorBox />
      </div>
    </ColorProvider>
  );
}

export default App;

이제 SelectColors에 나열된 색중 선택하면 아래 div color를 변경하게 하겠습니다.

src/components/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={{
                  background: 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()를 통해 이벤트 버블링을 막았습니다. 그래서 브라우저상 메뉴가 뜨지 않습니다.

마우스 왼쪽 버튼 클릭시 onClick으로 Setcolor를 onContextMenu를에서 setSubColor를 통해 context에서 관리하는 상태를 변경하고 있습니다.

todo

Hook 또는 contextType 사용하기

레퍼런스 : 리액트 16.3 Context API 파헤치기 - velopert

profile
서로 아는 것들을 공유해요~

0개의 댓글