본 포스팅은 'React Hook' 에 대한 시리즈 게시글 중 5번째 게시글로, useContext에 대해 중점적으로 다루고 있습니다!
useState()
, useEffect()
를 너머, 이제 공식 문서상 마지막 기본 hook 인 useContext()
hook 을 다룰 차례이다.
useContext()
가 우선 어떤 상황에 유용할 수 있는지 아래 예시를 바로 보면서 확인해보자.
(참고) 현재 폴더 구조는 아래와 같다
// src/common/useContext/index.js
import UserContainer from 'common/useContext/UserContainer'
import React, { useState } from 'react'
function UseContextPage() {
const [user, setUser] = useState({
name: "홍길동",
age: 20,
school: "SKKU",
})
return (
<div>
<h1>
UseContextPage
</h1>
<button onClick={() => { setUser({ ...user, name: "홍길순" }) }}>
유저 바꾸기
</button>
<UserContainer user={user} />
</div >
)
}
export default UseContextPage
// src/common/useContext/UserContainer/index.js
import UserCard from 'common/useContext/UserContainer/UserCard';
function UserContainer(props) {
return (
<div>
<h2>UserContainer</h2>
<p>UserContainer 컴포넌트에서 {JSON.stringify(props.user)} prop 을 받아왔습니다. </p>
<UserCard user={props.user} />
</div>
)
}
export default UserContainer
// src/common/useContext/UserContainer/UserCard/index.js
import UserName from 'common/useContext/UserContainer/UserCard/UserName'
import React from 'react'
function UserCard(props) {
return (
<div>
<h3>UserCard</h3>
<p>UserCard 컴포넌트에서 {JSON.stringify(props.user)} prop 을 받아왔습니다. </p>
<UserName user={props.user} />
</div>
)
}
export default UserCard
// src/common/useContext/UserContainer/UserCard/UserName/index.js
import React from 'react'
function UserName(props) {
return (
<div>
<h4>UserName</h4>
<p>UserCard 컴포넌트에서 {JSON.stringify(props.user)} prop 을 받아왔습니다. </p>
<p>사용자의 이름은: {props.user.name}</p>
</div>
)
}
export default UserName
길고 여러 파일에 나누어 져서 복잡한 코드처럼 보이지만, 실상은 아래의 결과를 내는 아주 단순한 코드이다.
여기서 무엇을 말하고나 하는 것일까?
코드를 잘 보면, UseContextPage
라는 최상단의 페이지 컴포넌트에서 제공되는 user
은 정작 컴포넌트 트리 저~ 아래의 UserName
에서 사용되고 있는데, 중간 컴포넌트들인UserContainer
와 UserCard
에서는 사용되지 않음에도 불고하고, UserName
이 필요하다고 하니까 중간에 단순히 이 데이터를 props 로 전달만 해 줄 수 밖에 없는 상황인 것이다.
즉, 특정 데이터가 필요 없는데 단순히 "전달" 목적으로 props 들이 내려가는 경우가 생긴다. 이를 가리키는 용어가 있는데 바로 prop drilling
이다.
해당 컴포넌트에서 사용하지도 않을 prop 을 단순히 전달 목적으로만 이렇게 내려주게 되면, 코드의 가독성이 떨어지고, 하부 컴포넌트로 갈수록 데이터에 문제가 생겼을 경우, 하나 하나 상위 컴포넌트를 추적해가며 잘못된 지점을 찾는 것이 어려워진다.
따라서 단순히 "전달" 만 하는데도 prop 을 넘겨 주는 경우들을 제거할 때 사용하는 것이 react 에서 기본 제공하는 Context API 이다.
여기서부터 사용되는 도식 및 그림 설명은 개발자 hoon5083 님의 자료를 참고했습니다 😀
현재의 문제를 나타내자면 아래와 같다.
만들어지고 사용되는 곳은 맨 위와 맨 아래 컴포넌트인데, 둘 사이에 UserContainer
과 UserCard
라는 컴포넌트를 타고 오는 것이다.
그래서 추적이 어려워지고, 코드를 읽기 어렵게 만드는 현상을 해결하고자, 아래와 같은 구조를 생각하게 된 것이다.
이런 식으로 작성을 하면 특정 data 가 필요하지 않은 컴포넌트는 불필요하게 prop 을 받지 않아도 돼서 더 클린한 코드를 유지할 수 있다. 아래와 같이 구현을 하면 된다.
import UserContainer from 'common/useContext/UserContainer'
import React, { createContext, useState } from 'react'
export const MyContext = createContext()
function UseContextPage() {
const [user, setUser] = useState({
name: "홍길동",
age: 20,
school: "SKKU",
})
return (
<MyContext.Provider value={user}>
<div>
<h1>
UseContextPage
</h1>
<button onClick={() => { setUser({ ...user, name: "홍길순" }) }}>유저 바꾸기</button>
<UserContainer />
</div >
</MyContext.Provider>
)
}
export default UseContextPage
// src/common/useContext/UserContainer/UserCard/UserName/index.js
import { MyContext } from 'common/useContext'
import React, { useContext } from 'react'
function UserName() {
const user = useContext(MyContext)
return (
<div>
<h4>UserName</h4>
<p>사용자의 이름은: {user.name}</p>
</div>
)
}
export default UserName
이런 식으로 user 데이터에 대한 context 를 생성해서 전역 상태로 넘겨 주면, 필요한 컴포넌트에서 useContext()
를 통해서 받아올 수가 있다!
Context 같은 경우 React 에서 기본적으로 제공하는 전역 상태 (관리) 툴 중 하나이다. 보통은 이보다 더 많은 기능을 제공하는 Redux 나 Zustand 도 많이 사용한다. 그리고 data fetching 과 함께 상태 관리를 하고 싶다면, SWR 도 많이 사용하는 편이다!
전역 상태 관리가 장점만 있는 것은 아니고, 단점도 있다. 우선, 컴포넌트 재사용이 어려워지며, 특정 전역 상태를 사용하는 컴포넌트는 그 상태가 변하면 전부 rerender 돼서 성능 이슈가 있을 수도 있다.
이렇게 보니 반가운 ppt 페이지 군요 하하