React.js - Context API

Gyu·2022년 6월 28일
0
post-thumbnail

Context API란?

  • Context API는 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을 때 유용한 기능이다. 리덕스, 리액트 라우터, styled-components 등의 라이브러리는 Context API 기반으로 구현되어있다.
  • 프로젝트 내에서 환경설정, 사용자 정보와 같은 전역적으로 필요한 상태를 관리해야할 때 유용하다.
  • Context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이다. Context는 모든 레벨의 컴포넌트 트리에 props를 통하지 않아도 데이터를 전달할 수 있는 방법을 제공해준다.

Context API를 사용하는 이유

  • 리액트 애플리케이션은 컴포넌트 간에 데이터를 props로 전달하기 때문에 컴포넌트 여기저기에서 필요한 데이터가 있을 경우 주로 최상위 컴포넌트인 App의 state에 넣어서 관리한다. 만약 뎁스가 깊은 프로젝트일 경우 루트에서 자식까지 데이터를 전달할 때 중복 코드가 발생하고, 그럴 경우 유지보수 효울성이 낮아진다.
  • 하지만 context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.
  • 때문에 리덕스나 MobX같은 상태관리 라이브러리를 사용하여 전역 상태관리 작업을 편하게 처리한다. 리액트 16.3v 업데이트 이후에는 Context API가 맣이 개선되었기 때문에 별도 라이브러이 없이 전역 상태를 손쉽게 관리할 수 있다.

Context API 사용하기

Context 객체 만들기

  • React.createContext(defaultValue)
    • Context 객체를 만드는 함수. Context 객체를 구독하고 있는 컴포넌트를 렌더링할 때 React는 트리 상위에서 가장 가까이 있는 짝이 맞는 Provider로부터 현재값을 읽는다.
    • defaultValue 매개변수는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값이다. 이 기본값은 컴포넌트를 독립적으로 테스트할 때 유용하다. Provider를 통해 undefined을 값으로 보낸다고 해도 구독 컴포넌트들이 defaultValue 를 읽지는 않는다.
  • // context/color.js
    import { createContext } from "react"; // 1. import createContext
    
    // 2. create context object
    // setting defaultValue is optionable
    const ColorContext = createContext({color: 'black'});
    
    // 3. export
    export default ColorContext;
  • 이렇게 만들어진 Context 객체는 Consumer 컴포넌트와 Provider 컴포넌트를 프로퍼티로 갖는다.
    • Consumemr : Context에서 설정한 값을 읽을 때 사용
    • Provider : Context 에서 사용 할 값을 설정할 때 사용

Consumer 사용하기

<MyContext.Consumer>
  {value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>
  • Consumer는 Context 변화를 구독하는 역할을 한다. 즉 Context에서 설정한 값을 컴포넌트 내에서 조회 때 사용하는 컴포넌트다. 이 컴포넌트를 사용하면 함수 컴포넌트안에서 context를 구독할 수 있다.
  • Context.Consumer의 자식은 함수여야한다. 이 함수는 context의 현재값을 매개변수로 받고 React 노드를 반환한다. 이 함수가 받는 value 매개변수 값은 해당 context의 Provider 중 상위 트리에서 가장 가까운 Provider의 value prop과 동일하다. 상위에 Provider가 없다면 value 매개변수 값은 createContext()에 보냈던 defaultValue와 동일하다.
  • Consumer 사용시 중요한 것은 Consumer 사이에 중괄호를 열어 그 안에 함수를 넣는 것이다. 이렇게 컴포넌트의 child가 있어야할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 넣는 패턴을 Function as a child 혹은 Render Props라고 한다.
  • import React from 'react';
    import ColorContext from '../context/color';
    
    const ColorBox = () => {
      return (
        <ColorContext.Consumer>
          {
            value => (
              <div style={{
                  width: '64px',
                  height: '64px',
                  background: value.color
              }} />
            )
          }
        </ColorContext.Consumer>
      );
    };
    
    export default ColorBox;

Provide 사용하기

<MyContext.Provider value={/* 어떤 값 */}>
  • Context를 구독하는 컴포넌트들에게 Context의 변화를 알리는 역할을 한다. Provider 컴포넌트는 value prop을 받아서 이 값을 하위에 있는 컴포넌트에게 전달한다. 값을 전달받을 수 있는 컴포넌트의 수에 제한은 없다. Provider 하위에 또 다른 Provider를 배치하는 것도 가능하며, 이 경우 하위 Provider의 값이 우선시된다.
  • Provider 하위에서 Context를 구독하는 모든 컴포넌트는 Provider의 value prop가 바뀔 때마다 다시 렌더링 된다. Provider로부터 하위 consumer(.contextType와 useContext을 포함한)로의 전파는 shouldComponentUpdate 메서드가 적용되지 않으므로, 상위 컴포넌트가 업데이트를 건너 뛰더라도 consumer가 업데이트된다.
  • createContext 함수를 사용할 때 기본값으로 넣어준 defaultValue 매개변수는 Provider를 사용하지 않을 때만 유효하다. Provicer를 사용했는데 value를 명시하지 않으면, 기본값을 사용하지 않기 때문에 오류가 발생한다.
  • import './App.css';
    import ColorBox from './components/ColorBox';
    import ColorContext from './context/color';
    
    function App() {
      return (
        <ColorContext.Provider value={{color: 'red'}}> // value props 명시 필수!
          <div className="App">
            <ColorBox />
          </div>
        </ColorContext.Provider>
      );
    }
    
    export default App;

동적 Context 사용하기

  • 색상선택 예제로 동적 context 사용하는 법 배우기

1. context 만들기

// context.color.js : 색상관련 context 파일
import React, { createContext, useState } from "react";

// 1. createContext함수로 context 생성
// 기본값은 Provider의 value에 넣을 객체의 형태와 일키시켜 주는 것이 좋다.
const ColorContext = createContext({ 
  state: {
    color: 'black',
    subcolor: 'red'
  },
  actions: {
    setColor: () => {},
    setSubcolor: () => {}
  }
});

// 2. ColorContext.Provider를 렌더링하는 컴포넌트 생성
// Provider의 value에는 상태로 state를, 업데이트 함수는 actions로 묶어서 전달
// Context에서 값을 동적으로 사용할 때 반드시 묶어줄 필요는 없지만
// 이렇게 state와 actions 객체를 따로 분리해주면 나중에 다른 컴포넌트에서
// Context의 값을 사용할 때 편하다.
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

// 2. Provider와 Consumer export
export { ColorProvider, ColorConsumer }

export default ColorContext;

2. Consumer 사용

// components/ColorBox.js : color context의 state를 전달받아 보여주는 컴포넌트
import React from 'react';
// 1. import ColorConsumer component
import { ColorConsumer } from '../context/color';

const ColorBox = () => {
    return (
      // 2. ColorConsumer 컴포넌트 사용
      // Consumer 컴포넌트의 child는 함수여야 한다.
      // child 함수의 매개변수는 비구조화할당을 이용해 간단히 value의 state만 조회한다.
      <ColorConsumer>
        {
          ({state}) => (
            <>
              <div style={{
                  width: '64px',
                  height: '64px',
                  background: state.color
              }} />

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

export default ColorBox;

3. Provider 사용

// components/SelectColor.js : 색상 선택 컴포넌트
// color context의 actions를 전달 받음
import React from 'react';
// 1. import consumer component
import { ColorConsumer } from '../context/color';

// 2. 색상 배열 생성
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];

const SelectColor = () => {
  return (
    <div>
      <h2>Select color</h2>
      // 3. consumer 컴포넌트 렌더링
      // child 함수의 매개변수에는 value의 actions 할당
      <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
                    onContextMenu={e => {
                      e.preventDefault();
                      actions.setSubcolor(color);
                    }}
                  />
                ))
              }
            </div> 
          )
        }
      </ColorConsumer>
      <hr />
    </div>
  );
};

export default SelectColor;
// 4. App.js에서 Provider 컴포넌트를 import해 렌더링
import './App.css';
import ColorBox from './components/ColorBox';
import { ColorProvider } from './context/color';
import SelectColor from './components/SelectColor';

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

export default App;

Consumer 대체 문법

함수형 컴포넌트

const value = useContext(Context);
  • 함수형 컴포넌트에서 useContext Hook을 사용하면 Context를 편하게 사용할 수 있다.
// components/ColorBox.js
// 1. import useContext
import React, {useContext} from 'react';
import ColorContext from '../context/color';

const ColorBox = () => {
  // 2. useContext 매개변수로 context 할당
  // useContext를 사용하면 children에 함수를 전달하는 Render Props 패턴 없이
  // Context 값을 조회할 수 있다.
  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 ColorBox;

클래스형 컴포넌트

  • 클래스형 컴포넌트에서는 static contextType을 정의하면 Context를 쉽게 사용할 수 있다. 클래스 상단에 static contextType를 정의하면 this.context로 Context에 접근할 수 있다.
  • 단 클래스형 컴포넌트에서는 한 클래스에서 하나의 Context 밖에 사용하지 못한다.
  • 		import React, { Component } from 'react';
    import ColorContext from '../contexts/color';
    
    const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
    
    		class SelectColors extends Component {
      // 1. static contextType 정의
      static contextType = ColorContext;
    
      // 2. this.context로 Context의 value에 접근
      handleSetColor = color => {
        this.context.actions.setColor(color);
      };
    
      handleSetSubcolor = subcolor => {
        this.context.actions.setSubcolor(subcolor);
      };
    
      render() {
        return (
          <div>
            <h2>색상을 선택하세요.</h2>
            <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>
            <hr />
          </div>
        );
      }
    }
    
    export default SelectColors;
profile
애기 프론트 엔드 개발자

0개의 댓글