이번 편에서는 React Context를 조금 더 편하게 사용할 수 있는 useContext를 알아보겠습니다.
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>
            </div>
          )
        }}
      </PostsContext.Consumer>
    )}
  </AppContext.Consumer>
)
Children 컴포넌트가 user와 post를 모두 보여주는 억지스러운 예시이지만, 쉽게 이해할 수 있도록 간단하게 표현했습니다.
React Context는 Props Drilling 패턴을 떨칠 수 있고 유연한 컴포넌트를 작성할 수 있다는 부분에서 좋은 기능인 것은 분명하지만, 여러 Context를 중첩해서 사용하다 보면 간단 기능을 담당하는 컴포넌트가 점점 이상하게 변하기 시작합니다. 더 나은 방법이 필요하다는 생각이 드는 순간입니다.
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>
    </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를 사용하는 것처럼 작성할 수 있으며, 코드를 읽기도 한결 더 수월합니다.
좋은 강의 감사합니다.
마지막 소스 적용한 전체 소스가 궁금해서
두 번째 소스를 바탕으로 테스트 페이지 만들었습니다.
https://codesandbox.io/s/inspiring-forest-6f6lq
소스 파일을 두 개로 분기 했고 두번째 소스의 사소한 버그 하나 수정했습니다.
좋아요와 댓글 감사합니다.
오탈자, 질문 등은 언제든지 댓글로 달아주세요!