โ ๏ธ ์ ๋ฆฌํ ๋ด์ฉ์ ์คํ๋ ์๋ชป๋ ์ ๋ณด๊ฐ ์์ ์ ์์ต๋๋ค. ๋๊ธ๋ก ์๋ ค์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.
context
๋ React ์ปดํฌ๋ํธ ํธ๋ฆฌ ์์์ ์ ์ญ์ ์ด๋ผ๊ณ ๋ณผ ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ ์ ์๋๋ก ๊ณ ์๋ ๋ฐฉ๋ฒ์ด๋ค.
์๋ ์ฝ๋๋ ๋ฒํผ ์ปดํฌ๋ํธ๋ฅผ ๊พธ๋ฏธ๊ธฐ ์ํด theme props๋ฅผ ๋ช ์์ ์ผ๋ก ๋๊ฒจ์ฃผ๊ณ ์๋ค.
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar ์ปดํฌ๋ํธ๋ ๋ถํ์ํ ํ
๋ง prop๋ฅผ ๋ฐ์์
// ThemeButton์ ์ ๋ฌํด์ผ ํฉ๋๋ค.
// ์ฑ ์์ ๋ชจ๋ ๋ฒํผ์ด ํ
๋ง๋ฅผ ์์์ผ ํ๋ค๋ฉด
// ์ด ์ ๋ณด๋ฅผ ์ผ์ผ์ด ๋๊ธฐ๋ ๊ณผ์ ์ ๋งค์ฐ ๊ณคํน์ค๋ฌ์ธ ์ ์์ต๋๋ค.
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
context
๋ฅผ ์ฌ์ฉํ๋ฉด ์ค๊ฐ์ ์๋ ์๋ฆฌ๋จผํธ๋ค์๊ฒ props๋ฅผ ๋๊ฒจ์ฃผ์ง ์์๋ ๋๋ค.
// ThemeContext ๋ง๋ค๊ธฐ
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Provider๋ฅผ ์ด์ฉํด ํ์ ํธ๋ฆฌ์ ํ
๋ง ๊ฐ์ ๋ณด๋ด์ค๋ค.
// ์๋ ์์์์๋ dark๋ฅผ ํ์ฌ ์ ํ๋ ํ
๋ง ๊ฐ์ผ๋ก ๋ณด๋ธ๋ค.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// ์ด์ ์ค๊ฐ์ ์๋ ์ปดํฌ๋ํธ๊ฐ ์ผ์ผ์ด ํ
๋ง๋ฅผ ๋๊ฒจ์ค ํ์๊ฐ ์๋ค.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// ํ์ฌ ์ ํ๋ ํ
๋ง ๊ฐ์ ์ฝ๊ธฐ ์ํด contextType์ ์ง์ ํ๊ณ ๊ฐ์ฅ ๊ฐ๊น์ด ์๋ ํ
๋ง Provider๋ฅผ
// ์ฐพ์ ๊ทธ ๊ฐ์ ์ฌ์ฉ, ํ์ฌ ์ ํ๋ ํ
๋ง๋ dark์ด๋ค.
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
context
์ ์ฃผ๋ ์ฉ๋๋ ๋ค์ํ ๋ ๋ฒจ์ย ๋ง์ย ์ปดํฌ๋ํธ์๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ด๋ค. ํ์ง๋ง context
๋ฅผ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฌ์ฉํ๊ธฐ๊ฐ ์ด๋ ค์์ง
์ฌ๋ฌ ๋ ๋ฒจ์ ๊ฑธ์ณ props ๋๊ธฐ๋ ๊ฑธ ๋์ฒดํ๋ ๋ฐ์ context
๋ณด๋คย ์ปดํฌ๋ํธ ํฉ์ฑ์ด ๋ ๊ฐ๋จํ ํด๊ฒฐ์ฑ
์ผ ์๋ ์๋ค.
์๋์ฒ๋ผ props๋ฅผ ๊ณ์ ์ ๋ฌํด์ผํ๋ ๊ฒฝ์ฐ
<Page user={user} avatarSize={avatarSize} />
<PageLayout user={user} avatarSize={avatarSize} />
<NavigationBar user={user} avatarSize={avatarSize} />
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
Avatar ์ปดํฌ๋ํธ ์์ฒด๋ฅผ ๋๊ฒจ์ฃผ๋ฉด context๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์ด๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
<Page user={user} avatarSize={avatarSize} />
<PageLayout userLink={...} />
<NavigationBar userLink={...} />
{props.userLink}
์ ์ด์ ์ญ์ (IoC)์ ์ด์ฉํ๋ฉด ๋๊ฒจ์ค์ผ ํ๋ props ์๋ ์ค๊ณ ์ต์์ ์ปดํฌ๋ํธ์ ์ ์ด๋ ฅ์ ๋ ์ปค์ง๊ธฐ ๋๋ฌด๋น ๋ ๊น๋ํ ์ฝ๋๋ฅผ ์ธ ์ ์๋ค. ํ์ง๋ง ๋ณต์กํ ๋ก์ง์ ์์๋ก ์ฌ๋ฆฌ๋ฉด ์ปดํฌ๋ํธ๋ ๋ ๋ํดํด์ง๊ณ ํ์ ์ปดํฌ๋ํธ๋ ๋ ์ ์ฐํด์ ธ์ผ ํ๊ธฐ ๋๋ฌธ์ ํญ์ ์ณ์ ๋ฐฉ๋ฒ์ ์๋๋ค.
๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ํธ๋ฆฌ ์ ์ฌ๋ฌ ๋ ๋ฒจ์ด ์๋ ๋ง์ ์ปดํฌ๋ํธ์ ์ฃผ์ด์ผ ํ ๊ฒฝ์ฐ, ๋ฐ์ดํฐ ๊ฐ์ด ๋ณํ ๋๋ง๋ค ๋ชจ๋ ์ปดํฌ๋ํธ์ ๋๋ฆฌ ๋งํ๋ ๊ฒ์ด context
์ด๋ค.
const MyContext = React.createContext(defaultValue);
context
๊ฐ์ฒด๋ฅผ ๋ง๋ ๋ค. Context
๊ฐ์ฒด๋ฅผ ๊ตฌ๋
ํ๊ณ ์๋ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ ๋ React๋ ํธ๋ฆฌ ์์์์ ๊ฐ์ฅ ๊ฐ๊น์ด ์๋ ์ง์ด ๋ง๋ Provider
๋ก๋ถํฐ ํ์ฌ ๊ฐ์ ์ฝ๋๋ค.
defaultValue
๋ ์ ์ ํ Provider๋ฅผ ์ฐพ์ง ๋ชปํ์ ๋๋ง ์ฐ์ด๋ ๊ฐ์ด๋ค. ์ฆ, ๊ฐ์ ๋ฃ์ด๋ provider
๊ฐ ์กด์ฌํ๋ฉด ์ฝ์ง ์๋๋ค.
<MyContext.Provider value={/* ์ด๋ค ๊ฐ */}>
Provider
๋ context
๋ฅผ ๊ตฌ๋
ํ๋ ์ปดํฌ๋ํธ๋ค์๊ฒ context
์ ๋ณํ๋ฅผ ์๋ฆฌ๋ ์ญํ ์ ํ๋ค. value
prop์ ๋ฐ์์ ์ด ๊ฐ์ ํ์์ ์๋ ์ปดํฌ๋ํธ์๊ฒ ์ ๋ฌํ๋ค.
Provider
์ value
๊ฐ ๋ฐ๋ ๋๋ง๋ค ๋ค์ ๋ ๋๋ง ๋๋ค. ๊ฐ์ด ๋ฐ๋์๋์ง ์ฌ๋ถ๋ Object.is
์ ๋์ผํ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ๋ค.
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* MyContext์ ๊ฐ์ ์ด์ฉํ ์ฝ๋ */
}
componentDidUpdate() {
let value = this.context;
}
componentWillUnmount() {
let value = this.context;
}
render() {
let value = this.context;
}
}
MyClass.contextType = MyContext;
React.createContext()
๋ก ์์ฑํ Context ๊ฐ์ฒด๋ฅผ ์ํ๋ ํด๋์ค์ย contextType
ํ๋กํผํฐ๋ก ์ง์ ํ ์ ์๋ค. ์ด ํ๋กํผํฐ๋ฅผ ํ์ฉํด ํด๋์ค ์์์ย this.context
๋ฅผ ์ด์ฉํด ํด๋น Context์ ๊ฐ์ฅ ๊ฐ๊น์ด Provider๋ฅผ ์ฐพ์ ๊ทธ ๊ฐ์ ์ฝ์ ์ ์๊ณ ์ด ๊ฐ์ render๋ฅผ ํฌํจํ ๋ชจ๋ ์ปดํฌ๋ํธ ์๋ช
์ฃผ๊ธฐ ๋งค์๋์์ ์ฌ์ฉํ ์ ์๋ค.
๋จ, Class.contextType
์ ์ฌ์ฉํ๋ฉด ํ๋์ context๋ง ๊ตฌ๋
ํ ์ ์๋ค.
<MyContext.Consumer>
{value => /* context ๊ฐ์ ์ด์ฉํ ๋ ๋๋ง */}
</MyContext.Consumer>
context ๋ณํ๋ฅผ ๊ตฌ๋
ํ๋ React ์ปดํฌ๋ํธ์ด๋ค. ์ด ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ฉด ํจ์ ์ปดํฌ๋ํธ ์์์ context๋ฅผ ๊ตฌ๋
ํ ์ ์๋ค. Context.Consumer
์ ์์์ ํจ์์ฌ์ผ ํ๋ค. ์ด ํจ์๋ context์ ํ์ฌ๊ฐ์ ๋ฐ๊ณ React๋
ธ๋๋ฅผ ๋ฐํํ๋ค.
Context ๊ฐ์ฒด๋ displayName ๋ฌธ์์ด ์์ฑ์ ์ค์ ํ ์ ์๋ค. ์ด ๋ฌธ์์ด์ ์ฌ์ฉํด์ context๋ฅผ ์ด๋ป๊ฒ ๋ณด ์ฌ์ค ์ง ๊ฒฐ์ ํ๋ค.
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools
const value = useContext(MyContext);
context๊ฐ์ฒด๋ฅผ ๋ฐ์ ๊ทธ context์ ํ์ฌ ๊ฐ์ ๋ฐํํ๋ค. context์ ํ์ฌ ๊ฐ์ ํธ๋ฆฌ ์์์ ์ด Hook์ ํธ์ถํ๋ ์ปดํฌ๋ํธ์ ๊ฐ์ฅ ๊ฐ๊น์ด์ ์๋ย <MyContext.Provider>
์ย value
ย prop์ ์ํด ๊ฒฐ์ ๋๋ค.
useContext
๋ฅผ ํธ์ถํ ์ปดํฌ๋ํธ๋ context ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด ํญ์ ๋ฆฌ๋ ๋๋ง ๋ ๊ฒ์
๋๋ค. ์ปดํฌ๋ํธ๋ฅผ ๋ฆฌ๋ ๋๋ง ํ๋ ๊ฒ์ ๋น์ฉ์ด ๋ง์ด ๋ ๋ค๋ฉด,ย ๋ฉ๋ชจ์ด์ ์ด์
์ ์ฌ์ฉํด ์ต์ ํํ ์ ์๋ค.
useContext(MyContext)
๋ ํด๋์ค์์์ย static contextType = MyContext
ย ๋๋ย <MyContext.Consumer>
์ ๊ฐ๋ค๊ณ ๋ณผ ์ ์๋ค.
๋ค์ ๋ ๋๋งํ ์ง ์ฌ๋ถ๋ฅผ ์ ํ ๋ ์ฐธ์กฐ๋ฅผ ํ์ธํ๊ธฐ ๋๋ฌธ์, Provider์ ๋ถ๋ชจ๊ฐ ๋ ๋๋ง ๋ ๋๋ง๋ค ๋ถํ์ํ๊ฒ ํ์ ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ ๋๋ง ๋๋ ๋ฌธ์ ๊ฐ ์๊ธธ ์๋ ์๋ค.
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}
์ด๋ฅผ ํผํ๊ธฐ ์ํด์๋ ๋ถ๋ชจ์ state๊ฐ์ผ๋ก ๋์ด์ฌ๋ฆฌ๋ฉด ๋๋ค.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<MyContext.Provider value={this.state.value}>
<Toolbar />
</MyContext.Provider>
);
}
}
UserContext/index.js
import React, { createContext, useContext, useReducer } from 'react'
import useActions from './actions'
import { reducer } from './reducer'
export const useUserContext = () => {
const context = useContext(UserContext)
if (context === undefined) {
throw new Error('useUserContext was used outside of its Provider')
}
return context
}
const UserContext = createContext()
const UserContextProvider = ({ children }) => {
const [currentUserState, dispatch] = useReducer(reducer, initialUserData)
const { onAuth, onLogin, onLogout, onSignUp, onUpdate } = useActions(dispatch)
return (
<UserContext.Provider
value={{ currentUserState, onAuth, onLogin, onLogout, onSignUp, onUpdate }}
>
{children}
</UserContext.Provider>
)
}
export default UserContextProvider
useContextํ ์ ์ฌ์ฉํด์ ๋ณํ๋ฅผ ๊ฐ์งํ์ฌ ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ ๋๋ง ํ๋๋ก ๊ตฌ์ฑํ์๋ค. ๋ํ useReducerํ ๋ ์ฌ์ฉํ๋ฉด์ ์ ์ ์ ๋ณด๋ฅผ ์ ๋ฐ์ดํธ ํ ์ ์๋๋ก ํ์๋ค.
๋ํ action์ value๋ก ๋๊ฒจ ํํด์ง action์ ๋ฐ๋ผ ๋ค๋ฅธ ์ํ ๋ณํ์ ํ ์ ์๋๋ก ๊ตฌํํ์๋ค.
App.js
import UserContextProvider from '@contexts/UserContext'
import AppRouter from '@routes'
function App() {
return (
<UserContextProvider>
<AppRouter />
</UserContextProvider>
)
}
export default App
App.js ์ฆ ๋ชจ๋ ํ์ด์ง์์ ์ ์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ App์ Provider๋ก ๊ฐ์ธ์ ์ ๋ฌ ํ ์ ์๋๋ก ํ์๋ค.
useActions
const useActions = (dispatch) => {
const onLogin = useCallback(
async (userInfo) => {
dispatch({ type: SET_LOADING })
const { user, token } = await login(userInfo)
setItem('jwt_token', token)
dispatch({ type: SET_USER, payload: { user, token } })
},
[dispatch]
)
return {
onLogin
}
}
export default useActions
useActions๋ฅผ ๋ง๋ค์ด dispatch๋ฉ์๋๋ก ํ์ ์ ๋ฃ์ด ์ํฉ์ ๋ง๊ฒ ์ํ๊ฐ ์ ์ฅ๋ ์ ์๋๋ก ํ์๋ค.
reducer
import { SET_USER } from './constants'
export const reducer = (state, { type, payload }) => {
switch (type) {
case SET_USER:
return {
...state,
currentUser: payload.user,
token: payload.token,
}
default:
throw new Error(`Unknown action type: ${type}`)
}
}
๋ค์๊ณผ ๊ฐ์ด reducer๋ฅผ ๋ง๋ค์ด dispatch ๋ฉ์๋์ ์ง์ ํํ๋ก state๋ฅผ ๋ฐํํ ์ ์๋๋ก ํ์๋ค.
๋ฌ๋ถ ํ๋ก์ ํธ๋ฅผ ์งํํ ๋ context๋ก ์ ์ ์ ๋ณด๋ฅผ ๊ด๋ฆฌ ํ๊ธฐ ์ํด ์ฌ์ฉํ์๋๋ฐ ์๊ฐ์ด ์กฐ๊ธ ์ง๋์ ์ด๋ป๊ฒ ๊ตฌํํ์๋์ง ์กฐ๊ธ ๊ฐ๋ฌผ๊ฐ๋ฌผํ๋ค.
์ด๋ฒ์ ๋ค์ ๊ณต๋ถํ๋ฉด์ ํ๋ก์ ํธ๋ฅผ ๋ฏ์ด๋ณด์๋๋ฐ ์ด๋ป๊ฒ context๋ฅผ ์ฌ์ฉํ๋์ง ํ๋ก์ ํธ ์์ ๋ฅผ ํตํด ๋ค์ ํ์ธํ ์ ์์ด์ ์ข์๋ค.