이번 편에서는 React Context를 조금 더 편하게 사용할 수 있는 useContext를 알아보겠습니다.

  • useContext는 React가 제공하는 React Hooks이지만, React Context를 먼저 이해해야 하기 때문에 미처 소개하지 못 헀습니다.

React Context 중첩

import React, { createContext } from 'react'

// 0. AppContext 생성
const AppContext = createContext()

const App = () => {
  const user = {
    nickname: 'danuel',
    isAdmin: true
  }

  return (
    <AppContext.Provider value={user}>
      <div>
        <Posts />
      </div>
    </AppContext.Provider>
  )
}

// 1. PostsContext 생성
const PostsContext = createContext()

const Posts = () => {
  const posts = [
    {
      title: 'useContext 알아보기',
      content: '이번 편에서는 React Context를 ...'
    }
  ]

  return (
    <PostsContext.Provider value={posts}>
      <Children />
    </PostsContext.Provider>
  )
}

// 2. user와 posts를 가져와 화면에 보여주기
const Children = () => (
  <AppContext.Consumer>
    {user => (
      <PostsContext.Consumer>
        {posts => {
          let label = 'user'
          if (user.isAdmin) {
            label = 'admin'
          }

          return (
            <div>
              <div>{label}</div>
              <div>{user.nickname}</div>
              <div>{posts.map((post, index) => (
                <div key={index}>
                  <div>{post.title}</div>
                  <div>{post.content}</div>
                </div>
              ))}
            </div>
          )
        }}
      </PostsContext.Consumer>
    )}
  </AppContext.Consumer>
)

Children 컴포넌트가 user와 post를 모두 보여주는 억지스러운 예시이지만, 쉽게 이해할 수 있도록 간단하게 표현했습니다.

React Context는 Props Drilling 패턴을 떨칠 수 있고 유연한 컴포넌트를 작성할 수 있다는 부분에서 좋은 기능인 것은 분명하지만, 여러 Context를 중첩해서 사용하다 보면 간단 기능을 담당하는 컴포넌트가 점점 이상하게 변하기 시작합니다. 더 나은 방법이 필요하다는 생각이 드는 순간입니다.

useContext

import React, { createContext, useContext } from 'react'

// 0. AppContext 생성
const AppContext = createContext()

const App = () => {
  const user = {
    nickname: 'danuel',
    isAdmin: true
  }

  return (
    <AppContext.Provider value={user}>
      <div>
        <Posts />
      </div>
    </AppContext.Provider>
  )
}

// 1. PostsContext 생성
const PostsContext = createContext()

const Posts = () => {
  const posts = [
    {
      title: 'useContext 알아보기',
      content: '이번 편에서는 React Context를 ...'
    }
  ]

  return (
    <PostsContext.Provider value={posts}>
      <Children />
    </PostsContext.Provider>
  )
}

// 2. user와 posts를 가져와 화면에 보여주기
const Children = () => {
  const user = useContext(AppContext)
  const posts = useContext(PostsContext)

  let label = 'user'
  if (user.isAdmin) {
    label = 'admin'
  }

  return (
    <div>
      <div>{label}</div>
      <div>{user.nickname}</div>
      <div>{posts.map((post, index) => (
        <div key={index}>
          <div>{post.title}</div>
          <div>{post.content}</div>
        </div>
      ))}
    </div>
  )
}

useContext를 이용하면 Context.Consumer로 컴포넌트를 작성할 때 보다 더 쉽고 편하고 직관적으로 작성할 수 있습니다. useState, useEffect 등 여러 React Hooks와 조합해서 사용하기에도 용이하다는 장점도 얻을 수 있습니다.

주의

import React, { createContext, useContext } from 'react'

const Context = createContext()

// ...

const Children = () => {
  const context0 = useContext(Context.Provider) // ERROR!!!
  const context1 = useContext(Context.Consumer) // ERROR!!!
  const context2 = useContext(Context) // OK

  // ...
}

useContext는 createContext 함수를 실행한 결과를 그대로 파라미터로 넘겨줘야 합니다.

import React, { createContext } from 'react'

const AppContext = createContext()

// ...

const User = () => {
  const appContext = useContext(AppContext)

  // ...
}

이 예시와 같이 사용하다 보면 여러 Context를 모두 인지하고 있어야 하고, Context를 전달하기 전에 추가적인 처리를 해주고자 한다면 유연성이 부족하다는 이슈가 있습니다.

import React, { createContext, useContext, useMemo } from 'react'

const AppContext = createContext()

const useAppContext = () => useContext(AppContext)

// ...

const User = () => {
  const appContext = useAppContext()

  // ...
}

이 예시와 같이 유의미한 이름을 지정하고 사용하면 Context를 인지할 필요 없이 Custom Hooks를 사용하는 것처럼 작성할 수 있으며, 코드를 읽기도 한결 더 수월합니다.