• Context API는 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을 때 유용한 기능
  • 사용 예
    • 로그인 정보
    • 애프리케이션 환경 설정
    • 테마
    • redux, react-router, styled-components 의 라이브러리
  • 컴포넌트 간에 데이터를 props로 저달하기 때문에 여기저기서 필요한 데이터가 있을 때는 주로 최상위 컴포넌트인 App의 state에 넣어서 관리
  • 하지만 props으로 아주 ~ 먼 컴포넌트에 전달하려면 여러개의 컴포넌트를 지나가야 하니... 유지 보수성이 낮아질 가능성이 있음
  • 위 문제점을 해결하기 위해서 redux나 MobX 같은 상태 관리 라이브러리를 사용하면 전역 상태 관리를 더 편하게 처리
  • 리액트 v16.3 업데이트 이후에는 Context API가 많이 개선되었기 때문에 별도의 라이브러리 사용할 필요 없음
import { createContext } from 'react';

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

export default ColorContext; 
import ColorBox from './components/ColorBox';
<ColorBox></ColorBox>

Provider 이용하기

  • Provider를 사용하면 Context의 값을 변경할 수 있다.
  • 아래와 같이 컴포넌트 ColorBoxColorContext.Provider를 통해 color 값을 변경할 수 있다.
  • Provider를 사용할때는 value 값을 명시해줘야 제대로 작동
      <ColorContext.Provider value={{ color: 'red' }}>
        <div>
          <ColorBox></ColorBox>
        </div>
      </ColorContext.Provider>

동적으로 사용하기

  • context의 value에는 상태값, 함수를 전달할 수 있다.
  • 아래와 같이 state, actions을 따로 정의한 ColorContext를 정의
  • ColorProvider 를 정의
import React, { createContext, useState } from 'react';

// 객체에서 state, actions을 분리해서 정의하면 나중에 사용하기가 편함
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;
import React from 'react';
import { ColorConsumer } from '../contexts/color';

const ColorBox = () => {
  return (
    <ColorConsumer>
      {(
        { state } // 객체 비구조화 할당 문법
      ) => (
        <>
          <div
            style={{
              width: '64px',
              height: '64px',
              background: state.color
            }}
          />
          <div
            style={{
              width: '32px',
              height: '32px',
              background: state.subcolor
            }}
          />
        </>
      )}
    </ColorConsumer>
  );
};

export default ColorBox;

사용하는 쪽에서는 아래와 같이

      <ColorProvider>
        <div>
          <ColorBox></ColorBox>
        </div>
      </ColorProvider>

색상 선택하는 컴포넌트 생성

  • Context에 들어있는 actions에 넣어준 함수를 호출하는 컴포넌트 생성
# SelectColor.js
import React from 'react';
import { ColorConsumer } from '../contexts/color';

const colors = [
  'red',
  'oragne',
  'yellow',
  'green',
  'blue',
  'indigo',
  'violoet'
];

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;
  • App.js에서는 SelectColors의 컴포넌트를 추가
      <ColorProvider>
        <div>
          <SelectColors></SelectColors>
          <ColorBox></ColorBox>
        </div>
      </ColorProvider>

useContext Hook 사용

  • 리액트 내장되어 있는 Hooks 중에서 useContext를 사용해보자
  • useContext Hook은 함수형 컴포넌트에서만 사용할 수 있다.
  • ColorBox를 아래와 같이 수정
import React, { useContext } from 'react';
import { ColorContext } from '../contexts/color';

const ColorBox = () => {
  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을 정의
  • 기존에 SelectColors의 컴포넌트를 클래스형으로 변경
  • static contextType을 정의하면 클래스 메소드에서도 Context에 있는 함수 호출이 가능
  • 단점이라면 한 클래스에서 하나의 context만 사용할 수 있음
  • useContext를 사용하는게 더 권장됨
  • 아래가 기존
import React from 'react';
import { ColorConsumer } from '../contexts/color';

const colors = [
  'red',
  'oragne',
  'yellow',
  'green',
  'blue',
  'indigo',
  'violoet'
];

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;
  • 이후가 클래스 컴포넌트로 변경 후
import React, { Component } from 'react';
import ColorContext from '../contexts/color';

const colors = [
  'red',
  'oragne',
  'yellow',
  'green',
  'blue',
  'indigo',
  'violoet'
];

class SelectColors extends Component {
  static contextType = ColorContext;

  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; 

CSS를 Component별로 입히기 위해서는 styled-components를 사용하자

설치하기

yarn add styled-components

소스코드 예제

NewsListBlock의 css를 정의하고 rendering하는 곳에 CSS의 block으로 포장하면 정상적으로 css가 입힌다.

import React from 'react';
import styled from 'styled-components';
import NewsItem from './NewsItem';

const NewsListBlock = styled.div`
  box-sizing: border-box;
  padding-bottom: 3rem;
  iwidth: 768px;
  margin: 0 auto;
  margin-top: 2rem;
  @media screen and (max-width: 768px) {
    width: 100%;
    padding-left: 1rem;
    padding-right: 1rem;
  }
`;

const NewsList = () => {
  return (
    <NewsListBlock>  
      (...)
    </NewsListBlock>
  );
};

설치

yarn add axios

소스코드

import React, { useState } from 'react';
import axios from 'axios';

const News = () => {
  const [data, setData] = useState(null);

  // https://newsapi.org/s/south-korea-news-api
  // https://newsapi.org/v2/top-headlines?country=kr&apiKey=a8a9fcfc5218454aaf0b97113bb4f94c
  // https://newsapi.org/v2/top-headlines?country=kr&category=business&apiKey=a8a9fcfc5218454aaf0b97113bb4f94c
  // category: business, entertainment, health, science, sports, technology

  const onClick = async () => {
    try {
      const response = await axios.get(
        'https://newsapi.org/v2/top-headlines?country=kr&apiKey=a8a9fcfc5218454aaf0b97113bb4f94c'
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div>
      <button onClick={onClick}>불러오기</button>
      {data && (
        <textarea
          rows={7}
          value={JSON.stringify(data, null, 2)}
          readOnly={true}
        />
      )}
    </div>
  );
};

export default News;

REST API가 없고, React에서 데이터를 가지고와 화면에 뿌리는 예제를 하고 싶다면 JSONPlaceholder를 이용해 JSON 데이터 샘플을 사용을 할 수 있다. 주소는 여기로...

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(json => console.log(json))

또는

  const onClick = async () => {
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/todos/1'
      );
      setData(response.data);
    } catch (e) {
      console.log(e);
    }
  };

출력결과

{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

여러개의 메뉴가 있다고 가정해보자. 그 메뉴에서 내가 선택한 메뉴에 특정 스타일을 주고싶다.
사용자는 UI에서 내가 선택한 메뉴가 무엇인지 알아야 하니까! 이때 Link 대신 NavLink를 사용하면 된다.

NavLink는 링크가 활성화되었을때 특정 activeStyle을 사용해 스타일을 입힐 수 있다.

예제코드

  • 이전코드는 아래와 같다
<Link activeStyle={activeStyle} to="/locations/gyonggi">
  경기
</Link>
  • Link 대신 스타일을 입히기 위해서 NavLink를 사용하면 아래와 같다.
import React from 'react';
import { NavLink, Link, Route } from 'react-router-dom';
import Location from './Location';

const Locations = () => {
  const activeStyle = {
    background: 'red',
    color: 'yellow'
  };

  return (
    <div>
      <h1> 도시 목록 </h1>
      <ul>
        <li>
          <NavLink activeStyle={activeStyle} to="/locations/seoul">
            서울
          </NavLink>
        </li>
        <li>
          <NavLink activeStyle={activeStyle} to="/locations/gyonggi">
            경기
          </NavLink>
        </li>
      </ul>
      <Route
        path="/locations"
        exact={true}
        render={() => <div> 지역을 선택해 주세요. </div>}
      />
      <Route path="/locations/:cityname" component={Location} />
    </div>
  );
};

export default Locations;

Router를 이용해 URL로 접속했을때 어떤 컴포넌트로 가는지 정의를 했다.
이제는 URL로 접속했을때 존재하지 않는 URL에 대해서 처리가 필요하고,
단 하나의 라우트만 rendering 하도록 구현이 필요하다.

import React from 'react';
import { Route, Link, Switch } from 'react-router-dom';
import './App.css';

import Home from './components/Home';
import Search from './components/Search';
import Locations from './components/Locations';
import HistorySample from './components/HistorySample';

const App = () => {
  return (
    <div>
      <Link to="/">홈</Link>
      <Link to="/search">검색</Link>
      <Link to="/locations">위치</Link>
      <Link to="/history">히스토리</Link>
      <Switch>
        <Route path="/" component={Home} exact={true}></Route>
        <Route path="/search" component={Search}></Route>
        <Route path="/locations" component={Locations}></Route>
        <Route path="/history" component={HistorySample}></Route>
        <Route
          render={({ location }) => (
            <div>
              <h2> 이 페이지는 존재하지 않는다.</h2>
              <p>{location.pathname}</p>
            </div>
          )}></Route>
      </Switch>
    </div>
  );
};

export default App; 
  • 위 코드에서 path를 명시하지 않으면 해당하는 path가 없을때 해당 Route로 렌더링이 된다.
  • search, locations, history의 파라미터 URL이 아니라면 이 페이지는 존재하지 않는다.의 결과가 나온다.
  • withRouter 함수는 Higher-order Component
  • 라우터에 의해서 호출된 컴포넌트가 아니어도 match, location, history 객체에 접근할 수 있도록 해준다.

    참고로 history 는 match, location과 함께 전달되는 properties 중 하나로 페이지 이탈 방지할때 사용

import React from 'react';
import { withRouter } from 'react-router-dom';

const withRouterSample = ({ location, match, history }) => {
  return (
    <div>
      <h4>location</h4>
      <textarea
        value={JSON.stringify(location, null, 2)}
        rows={7}
        readOnly={true}></textarea>
      <h4>match</h4>
      <textarea
        value={JSON.stringify(match, null, 2)}
        rows={7}
        readOnly={true}></textarea>
      <button onClick={() => history.push('/')}>HOME</button>
    </div>
  );
};

export default withRouter(withRouterSample); 
  • 라우팅을 하는 컴포넌트가 있고, 그 라우팅된 컴포넌트에서 다른 컴포넌트를 사용할때 사용하면 된다.
  • 예를들면 도시의 목록이 있고, 그 도시 목록을 눌렀을때 아래 다른 컴포넌트가 표시될 필요가 있을때!
  • 검색된 결과를 받았을때 그 결과로 화면에 뿌려줘야 하는데 다른 컴포넌트에서도 쿼리가 필요할때

TypeError: Cannot read property 'createElement' of undefined 의 에러가 발생한다면 import 구문을 확인해보자 보통 우리는 여러개의 library를 { }을 사용해서 한번에 import를 하게 된다.

import { .... } from 'library';

클래스 컴포넌트를 생성하기 위해서는 Component를 import를 해야 하는데, 만약 아래와 같이 import 한다면 에러가 발생한다.

import { React, Component } from 'react';

issue 를 살펴보면 아래와 같이 선언을 하라고 한다.

import React, { Component } from 'react';

+ Recent posts