이 글은 velopert님의 Context API 를 사용한 전역 값 관리 부분을 공부하고 정리한 글입니다.
이전 포스팅에서 useReducer를 이용해 상태 관리 로직을 컴포넌트 외부로 분리시킨 적이 있다. 각종 액션을 디스패치하는 로직이 부모 컴포넌트의 onRemove, onToggle 등과 같은 함수로 정의하여 해당 로직을 사용하는 자식 컴포넌트에 props로 전달해주었다.
App → UserList → User
여기서 UserList 컴포넌트의 경우 onToggle과 onRemove를 전달하기 위해 중간다리 역할만 하고 있다.
function UserList({ users, onRemove, onToggle }) {
return (
<div>
{users.map(user => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
}
위와 같이 한개 정도 거쳐 전달하는 건 큰 불편함은 없지만, 만약 3-4개 이상의 컴포넌트를 거쳐서 전달해야 할 경우 매우 번거로워질 뿐만 아니라 실수가 발생할 수 있다.
이때 리액트의 Context API와 dispatch를 사용하면, 함수들을 props로 건네주지 않고 필요한 곳에서 바로 사용할 수 있게 된다.
리액트의 Context API를 사용하면, 프로젝트 안에서 전역적으로 사용할 수 있는 값(상태, 함수, 외부 라이브러리 인스턴스, DOM..)을 관리할 수 있다.
먼저 Context API를 사용해 새로운 Context를 만들어 만들어보자. 방법은 아래와 같이 React.createContext()
라는 함수를 사용하면 된다.
const UserDispatch=React.createContext(null);
Context를 만들면, Context 안에 Provider라는 컴포넌트가 있는데 이 컴포넌트를 통하여 Context의 값을 정할 수 있다. value에 useReducer로 만든 dispatch를 넣어주자.
<UserDispatch.Provider value={dispatch}>...</UserDispatch.Provider>
그러면 Provider에 의해 감싸진 하위 컴포넌트 어디서든지 Context의 값을 바로 조회해 사용할 수 있다.
우선 App 컴포넌트에서 Context를 만들어 export 해주고, Provider로 App 컴포넌트의 리턴값을 감싸주자.
(🌈 부분을 집중해서 보자)
//App.js
import React, { useReducer, useCallback } from 'react';
import UserList from './UserList';
const initialState = {
users: [
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active: true
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false
}
]
};
function reducer(state, action) {
switch (action.type) {
case 'TOGGLE_USER':
return {
...state,
users: state.users.map(user =>
user.id === action.id ? { ...user, active: !user.active } : user
)
};
case 'REMOVE_USER':
return {
...state,
users: state.users.filter(user => user.id !== action.id)
};
default:
return state;
}
}
// 🌈 UserDispatch 라는 이름으로 내보내면, 필요한 곳에서 import해 dispatch를 사용할 수 있게 된다.
export const UserDispatch = React.createContext(null);
function App() {
const [state, dispatch] = useReducer(reducer, initialState);//🌈
const { users } = state;
const onToggle = useCallback(id => {
dispatch({
type: 'TOGGLE_USER',
id
});
}, []);
const onRemove = useCallback(id => {
dispatch({
type: 'REMOVE_USER',
id
});
}, []);
return (
<UserDispatch.Provider value={dispatch}> //🌈
<UserList
users={users}
onToggle={onToggle}
onRemove={onRemove}
/>
</UserDispatch.Provider> //🌈
);
}
export default App;
위 코드에서 한 작업은 UserDispatch라는 Context를 만들어서 어디서든 dispatch를 꺼내 쓸 수 있도록 준비를 한 것이다.
아까 User에서 사용되는 onToggle과 onRemove를 UserList를 통해 props로 전달했다면, 이제는 User에서 바로 import {UserDispatch} from './App';
를 해서 dispatch를 꺼내 사용할 수 있게 됐다.
dispatch를 사용하려면, useContext라는 Hook을 사용하여 아까 만든 UserDispatch Context를 조회해야 한다.
// UserList.js
import React, { useContext } from 'react';
import { UserDispatch } from './App';
const User = React.memo(function User({ user }) {//🌈 onToggle, onRemove를 props로 받고 있지 않다.
const dispatch = useContext(UserDispatch); //🌈이제 dispath 함수를 사용할 수 있다.
return (
<div>
<b
style={{
cursor: 'pointer',
color: user.active ? 'green' : 'black'
}}
onClick={() => {
dispatch({ type: 'TOGGLE_USER', id: user.id });//🌈
}}
>
{user.username}
</b>
<span>({user.email})</span>
<button
onClick={() => {
dispatch({ type: 'REMOVE_USER', id: user.id });//🌈
}}
>
삭제
</button>
</div>
);
});
function UserList({ users }) {
return (
<div>
{users.map(user => (
<User user={user} key={user.id} />
))}
</div>
);
}
export default React.memo(UserList);
이렇게 Context API를 사용하여 dispatch를 어디서든지 조회해 사용할 수 있게 해주면 처음 구조보다 코드가 훨씰 깔끔해질 수 있다.