전역상태관리를 사용하지 않는 경우, 특정 컴포넌트에서의 state 변화를 다른 컴포넌트에게 전달하기 위해 여러차례 props를 전달해야한다.(이를 props drilling
이라고 부름)
Context API 사용법
createContext
라는 함수를 사용해서 Context 만든다.
(createContext라는 함수를 호출하면 Provider와 Consumer 라는 컴포넌트들이 반환된다.)Provider
는 Context에서 사용 할 값을 설정할 때 사용되고,Consumer
는 나중에 우리가 설정한 값을 불러와야 할 때 사용된다.
ThemeContext.js
import {createContext} from "react"
export const ThemeContext = createContext(null)
App.js
import {ThemeContext} from "./context/ThemeContext"
function App(){
const [isDark, setIsDark] = useState(false)
return (
<ThemeContext.Provider value={{isDark, setIsDark }}>
<Page />
</ThemeContext.Provider>
)
}
Page.js
function Page(){
return (
<div>
<Header/>
<Content />
<Footer />
</div>
)
}
Footer.js
import {ThemeContext} from "./context/ThemeContext"
function Footer(){
return (
<ThemeContext.Consumer>
{
({isDark, setIsDark}) => (
<div style={{backgroundColor: isDark ? "black" : "white"}}>
<button onClick={()=>setIsDark(!isDark)}>다크모드 버튼</button>
</div>
)
}
</ThemeContext.Consumer>
)
}
context를 사용하기 위해 render props 패턴을 사용하는 것이 조금 이상해 보일 수 있다. (render props 패턴 이해하기)
리액트 훅이 도입된 리액트 16.8부터는 context를 사용하는 다른 방법이 등장했다. 이제는 useContext 훅을 사용해 context를 사용할 수 있게 되었다.
Footer.js
import {useContext} from "react"
import {ThemeContext} from "./context/ThemeContext}
function Footer(){
const {isDark, setIsDark}=useContext(ThemeContext);
return (
<div style={{backgroundColor: isDark ? "black" : "white"}}>
<button onClick={()=>setIsDark(!isDark)}>다크모드 버튼</button>
</div>
)
}
공식문서에 따르면 context를 사용하면 컴포넌트를 재사용하기가 어려워지므로 꼭 필요할 때만 써야한다고 한다.
단순히 props drilling을 하지 않기 위해 context를 사용하는 것은 좋지 않고, 이런 경우 컴포넌트를 재사용하기가 어려워지므로이 더 간단한 해결책일 수도 있다고 한다.
props drilling을 해결하기 위해 컴포넌트 합성을 이용한 예시
예시1) 컴포넌트 합성 전
const Hello1 = (props) => (
<div>
this is Hello1.
<Hello2 name={props.name} />
</div>
);
const Hello2 = (props) => (
<div>
this is Hello2.
<Hello3 name={props.name} />
</div>
);
const Hello3 = (props) => (
<div>
this is Hello3.
<Hello4 name={props.name} />
</div>
);
const Hello4 = (props) => (
<div>
this is Hello4.
<div>Hello {props.name}!</div>
</div>
);
// App.tsx
<Hello1 name='asdf' />
-> 단지 에서 name을 사용하기 위해 props drilling을 통해 name을 출력하게 된다.
예시2) 컴포넌트 합성을 통해 해결
const Hello1 = (props) => (
<div>
this is Hello1.
{props.children}
</div>
);
const Hello2 = (props) => (
<div>
this is Hello2.
{props.children}
</div>
);
const Hello3 = (props) => (
<div>
this is Hello3.
{props.children}
</div>
);
const Hello4 = (props) => (
<div>
this is Hello4.
<div>Hello {props.name}!</div>
</div>
);
// App.jsx
const name = "은수";
<Hello1>
<Hello2>
<Hello3>
<Hello4 name={name} >
</Hello3>
</Hello2>
</Hello1>
-> 최상위 App.jsx 에서 모든 컴포넌트를 중첩하여 합성 컴포넌트 형태로 작성한다면, 나머지 컴포넌트에서는 props.children으로 랜더링을 해주고, 으로만 name을 전달해줄 수 있다. 즉, name의 값을 알아야하는건 App.jsx 뿐이다. (다른 component들은 name을 전달받지 않음.)
-> 하지만 이렇게 간단한 로직이 아닌 복잡한 로직을 상위에 작성한다면, 오히려 더 난해해질 수 있으니 각자의 로직에 알맞는 방법을 적용해야할 것이다.
value가 객체 일 때 하나만 바뀌어도 그 하나를 쓰지 않는 다른 자식 컴포넌트도 리렌더링 된다.
따라서 context분리가 필요하다.
(이내용은 더 알아봐야할 것 같다)
useContext 정리
- props drilling을 막기위해서 전역상태관리 도구를 사용해야하고 ContextAPI, Redux, Recoil, ReactQuery등을 쓸 수 있다.
- 원래는 render props 패턴으로 전역상태를 사용할 수 있었는데, useContext라는 React Hook이 나와서 useContext를 써서 전역상태를 사용할 수 있게 되었다.
- 단순히 props drilling을 막기위해서 Context를 쓰기보다는, 컴포넌트 합성도 고려해보자.
출처
React | React.Context 를 활용한 전역 상태 관리
React Hooks! useContext편(React 17)