프론트엔드에도 라우터 주소 설계는 매우 중요하다.
front/layouts/App/index.tsx
const App: FC = () => {
return (
<Switch>
<Redirect exact path="/" to="/login" />
<Route path="/login" component={LogIn} />
<Route path="/signup" component={SignUp} />
<Route path="/workspace/info" component={Workspace} />
<Route path="/workspace/:workspace" component={Workspace} />
</Switch>
);
};
export default App;
App 컴포넌트에서 Workspace의 path 부분에 나오는 :workspace
는 특수한 역할을 담당해서, 사용자가 자유롭게 값을 바꿀 수 있는 영역이 된다. 이를 라우트 파라미터라고 한다. (/workspace/sleact, /workspace/test 등 워크스페이스 이름별로 path를 분리할 수 있게됨)
위 /workspace/info
같은 파라미터가 아닌 path의 경우 반드시 하단 라우트 파라미터로 연결된 것보다 상위에 위치해야 제 주소를 찾아간다. 하위에 있을 경우 라우트 파라미터이 :workspace에 걸리기 때문이다.
front/layouts/Workspace/index.tsx
const Workspace: VFC = () => {
// ..
return (
<div>
<Header>
{/* code.. */}
</Header>
<WorkspaceWrapper>
{/* code.. */}
<Chats>
<Switch>
{/* 두 개의 중첩라우트 사용 가능 */}
<Route path="/workspace/:workspace/channel/:channel" exact component={Channel} />
<Route path="/workspace/dm/:id" exact component={DirectMessage} />
</Switch>
</Chats>
</WorkspaceWrapper>
{/* code.. */}
</div>
);
};
export default Workspace;
Workspace 내부의 라우팅 코드에도 라우트 파라미터를 적용해준다. 해당 내용을 로그인과 회원가입에 리다이렉팅 코드에 넣어주면 아래와 같이 할 수 있다.
front/pages/Login/index.tsx
, front/pages/SignUp/index.tsx
const SignUp(Login) = () => {
// ..
if (data) {
return <Redirect to="/workspace/sleact/channel/일반" />; // redirect path 수정
}
return (
<div id="container">
{/* code.. */}
</div>
);
};
export default SignUp;
위와 같이 설정하면 로그인인 시 /workspace/sleact/channel/일반
으로 페이지가 이동된다. Path 자체만으로도 현재 내가 있는 영역과 위치, 행동에 대해 추측할 수 있는 것이 바람직한 path 작성이라고 할 수 있음
이제 채널을 생성하는 CreateChannelModal 컴포넌트에 라우트파라미터를 코드에 적용시켜보자.
front/components/CreateChannelModal
import axios from 'axios';
import { useParams } from 'react-router';
import { toast, ToastContainer } from 'react-toastify';
interface Props {
show: boolean;
onCloseModal: () => void;
setShowCreateChannelModal: (flag: boolean) => void; // type 추가
}
const CreateChannelModal: VFC<Props> = ({ show, onCloseModal, setShowCreateChannelModal }) => {
const [newChannel, onChangeNewChannel, setNewChannel] = useInput('');
const { workspace, channel } = useParams<{ workspace: string; channel: string }>(); // 현재 위치 url의 Params를 가져온다.
const onCreateChannel = useCallback(
(e) => {
e.preventDefault();
axios
.post(
`/api/workspaces/${workspace}/channels`,
{
// 어떤 워크스페이스에 생성해야하는지를 모름
// > 현재 채널이 어디있는지 useParams로 체크한다.
name: newChannel,
},
{ withCredentials: true },
)
.then(() => {
setShowCreateChannelModal(false);
revalidateChannel(); // 채널리스트 다시 불러오기
setNewChannel('');
})
.catch((error) => {
console.dir(error);
toast.error(error.response?.data, { position: 'bottom-center' });
});
},
[newChannel],
);
return (
<>
{/* modal.. */}
<ToastContainer position="bottom-center" />
</>
);
};
export default CreateChannelModal;
front/layouts/Workspace/index.tsx
const Workspace: VFC = () => {
const {
data: userData, error, mutate: revalidateUser,
} = useSWR<IUser | false>('/api/users', fetcher, { dedupingInterval: 2000 });
// 현재 워크스페이스에 있는 채널들을 모두 가져오기
// 만약 로그인하지 않은 상태일 경우 null 처리하여 swr이 요청하지 않도록 처리한다. - 조건부 요청 지원함
const { data: channelData } = useSWR<IChannel[]>(userData ? `/api/workspaces/${workspace}/channels` : null, fetcher);
// events..
if (!userData) {
return <Redirect to="/login" />;
}
return (
<div>
<Header>
{/* code.. */}
</Header>
<WorkspaceWrapper>
{/* code.. */}
<Channels>
<WorkspaceName onClick={toggleWorkspaceModal}>Sleact</WorkspaceName>
<MenuScroll>
{/* code.. */}
{/* channelData가 있을 때 데이터를 아래 하단에 뿌려준다. */}
{channelData?.map((v) => (
<div key={v.name}>{v.name}</div>
))}
</MenuScroll>
</Channels>
{/* code.. */}
</WorkspaceWrapper>
{/* code.. */}
</div>
);
};
export default Workspace;
이는 workspace 컴포넌트에 useSWR을 추가하여 적용해주는데, const { data: channelData } = useSWR<IChannel[]>(userData ?
/api/workspaces/${workspace}/channels : null, fetcher);
이 코드를 보면 알겠지만 분기처리를 통해 값이 있을 경우에만 api request가 발생한다.