(React+Typescript) React-Router에서 Context Provider 사용하기
in Programming
오늘 해결한 이슈는 react-router와 Context API를 적절히 섞는 방법에 관한 문제였습니다. react-router의 Route를 사용하여 URI 기반으로 컴포넌트를 표시할 때 보통 Provider를 같이 제공해 줘야 합니다. 특히 Context API와 같이 사용할 때, 컴포넌트가 Context를 가지는 경우에 State를 관리하려면 Provider로 한 번 감싸 줘야 합니다. 그런데 Route 컴포넌트를 바로 Provider로 묶어 버리면 제대로 라우팅이 되지 않는 문제가 있습니다.
예를 들어, 기존에 Context 없이 다음과 같이 라우팅을 했다고 가정합시다.
// App.tsx
<BrowserRouter>
<Switch>
<Route path="/" exact component={Landing} />
<Route path="/event_detail" component={EventDetail} />
<Route path="/notice" component={Notice} />
<Redirect path="*" to="/" />
</Switch>
</BrowserRouter>
URI에 따라 각각 Landing, Eventdetail, Notice 컴포넌트를 반환하는 라우터입니다. 그런데 여기에서 EventDetail, Notice 각각의 컴포넌트에 Context가 부여되어야 한다면? 즉 Context API의 Provider를 한 번 거쳐야 한다면 어떻게 해야 할까요?
// App.tsx
<BrowserRouter>
<Switch>
<Route path="/" exact component={Landing} />
<EventDetailProvider>
<Route path="/event_detail" component={EventDetail} />
</EventDetailProvider>
<NoticeProvider>
<Route path="/notice" component={Notice} />
</NoticeProvider>
<Redirect path="*" to="/" />
</Switch>
</BrowserRouter>
간단하게 이렇게 각각의 Route를 감싸는 방법을 생각할 수 있습니다. 하지만 이렇게 감싸면 Route가 제대로 동작하지 않습니다. 그 이유는 <Switch>의 자식은 오직 <Route>와 <Redirect>만 허용되기 때문입니다. 즉 Switch-Provider-Route 구조가 아닌 Switch-Route-Provider 구조로 작성해야 제대로 라우팅을 할 수 있습니다.
그러니까 Provider 안에 Route를 넣지 말고, 아래와 같이 Route 안에 Provider를 넣으면 됩니다.
// App.tsx
<BrowserRouter>
<Switch>
<Route path="/" exact component={Landing} />
<Route path="/event_detail">
<EventDetailProvider>
<EventDetail />
</EventDetailProvider>
</Route>
<Route path="/notice">
<NoticeProvider>
<Notice />
</NoticeProvider>
</Route>
<Redirect path="*" to="/" />
</Switch>
</BrowserRouter>
Route에 직접 Component를 전달하지 않고 풀어서 작성할 수 있으므로, 내부에 명시적으로 Route-Provider-Component 구조를 작성해 주면 됩니다.
그런데 이렇게 일일히 Route-Provider-Component를 작성하게 되면 코드가 길어져 가독성이 상당히 떨어질 수 있습니다. 따라서 내부에 Provider를 포함해 주는 Context Router를 Typescript 버전으로 다음과 같이 작성할 수 있습니다.
// ContextRoute.tsx
type ContextRouterProps = {
path: string;
exact: boolean;
Provider: any;
Component: any;
}
const ContextRoute = (props: ContextRouterProps) => {
const { Provider, Component } = props;
return (
<Route path={props.path} exact={props.exact}>
<Provider>
<Component />
</Provider>
</Route>
);
}
export default ContextRoute;
이렇게 Provider와 Component를 props로 받고 순서대로 Route-Provider-Component 구조를 만들어 주면, 라우터를 아래와 같이 깔끔하게 작성할 수 있습니다.
// App.tsx
<BrowserRouter>
<Switch>
<Route path="/" exact component={Landing} />
<ContextRoute path="/event_detail" exact Provider={EventDetailProvider} Component={EventDetail} />
<ContextRoute path="/notice" exact Provider={NoticeProvider} Component={Notice} />
<Redirect path="*" to="/" />
</Switch>
</BrowserRouter>
기존 Route를 사용하듯이 ContextRoute도 동일한 맥락으로 사용할 수 있습니다. 이렇게 Provider를 제공하는 Router 외에도 유저 자동 인증을 해 주는 Router 등 여러가지로 응용할 수 있겠네요.