리액트 공식 홈페이지에 나와있는 Context의 설명은 다음과 같다.
context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있습니다.
리덕스의 store와 비슷한 개념으로, 어플리케이션 전역 스코프에서 가지는 상태라고 할 수 있다.
Context API 자체는 등장한지 꽤 되었지만 이러한 전역 상태관리 방법이 왜 필요한지는 리액트로 규모가 어느정도 되는 어플리케이션을 제작해본 사람이라면 바로 느낄것이다.
코딩 애플님의 영상인데, 왜 전역상태 관리가 필요한지 바로 느낄것이다. 어플리케이션의 규모가 조금만 커지게 되더라도 수많은 state와 props가 난무하게 되는데, 이걸 관리하기가 힘들어 지는 것 이다.
기존에 state를 선언했던 방식에서 React.createContext()
로 대체할 수 있다.
현재 나의 어플리케이션은 친구 목록을 json 파일에 저장되어 있고, 이를 최상위 컴포넌트에서 state로 선언한 다음 하위 컴포넌트에서 props로 받아서 사용하는 형태이다.
function ChatApp() {
/*
페이지를 처음 로딩할 때, 채팅 정보가 들어있는 json 파일을 불러온다.
이후 이 json 파일을 localStorage에 저장하여 component간 주고받는다.
*/
useEffect(() => {
localStorage.setItem('ChatList', JSON.stringify(Chats));
}, []);
return (
<Fragment>
<GlobalStyle />
<ChatAppContainer>
<Router>
<Route
exact
path={['/', '/chatlist', '/settings']}
component={NavBar}
/>
<Switch>
<Route exact path="/">
<FriendsList friends={Friends} /> // 친구 목록을 props로 전달
</Route>
<Route path="/chatlist">
<ChatList friends={Friends} key={Date.now()} />
</Route>
<Route path="/settings">
<Settings />
</Route>
</Switch>
</Router>
</ChatAppContainer>
</Fragment>
);
}
하위 컴포넌트가 여러겹 구조를 가지고 있다 보니까 상태관리가 너무 힘들어지는 감이 있었다.
/* userContext.tsx */
import Friends from '../data/Friends.json';
type friend = {
id: number;
name: string;
statusMessage: string;
profileImage: string;
};
const UserContext: React.Context<friend[]> = createContext([
{
id: 0,
name: '',
statusMessage: '',
profileImage: '',
},
]);
const UserContextProvider = ({ children }: any) => {
const [friends] = useState(Friends);
return (
<UserContext.Provider value={friends}>{children}</UserContext.Provider>
);
};
export default UserContextProvider;
export { UserContext };
export type { friend };
JSON으로 존재하던 친구 목록을 불러와 context로 선언하고 이를 사용할 provider 컴포넌트를 정의하였다. 이 provider는 최상위 컴포넌트를 감싸게 된다.
이와 관련해서 createContext()
의 인자의 타입에 따라 UserContext.Provider
의 value
의 타입이 결정되었다. 묵시적으로 타입을 선언해도 타입 추론이 가능하다는 점이 신기했다. 이래서 타입스크립트를 쓰는건지...
Context를 선언함에 따라, 이 컨텍스트를 유용하게 활용할 수 있는 custom hooks 또한 만들어야 했다.
/* useUserContext.ts */
import { useContext } from 'react';
import { friend, UserContext } from '../contexts/userContext';
const useUserContext = (): [() => friend[]] => {
const friends = useContext(UserContext);
const getFriendList = (): friend[] => friends;
return [getFriendList];
};
export default useUserContext;
Context를 사용함에 따라 기존에 props로 불러오던 친구의 목록을 더욱 편하게 관리할 수 있게 되었다.
/* FriendList.js */
function FriendsList(props) {
const [searchQuery, setSearchQuery] = useState('');
const [friendList, setFriendList] = useState(props.friends);
// some codes...
이랬었던 기존의 코드를
function FriendsList(props: any) {
const [searchQuery, setSearchQuery] = useState('');
const [getFriendList] = useUserContext();
const friendList = getFriendList();
이렇게 수정하였다. 현재는 친구 목록 불러오기가 기능의 전부이지만, 추후 친구 추가, 삭제, 내 정보 수정등 user와 관련된 기능들을 컨텍스트에 추가하게 된다면(리듀서를 사용해서) 컨텍스트를 통한 상태관리의 이점이 더욱 드러날 것으로 기대한다.