개발기간: 05/03 ~ 05/14
상태를 모든 화면에서 공유하는 형태의 프로젝트 상태관리가 힘들었지만 리액트가 아니었으면 더 난감했을 뻔했다.
이번 프로젝트에는 react-router를 사용했는데 기본적인 Link
, BrowserRouter(Router)
, Route
를 만들어보기도 했다.
먼저 react-router에 대해 작성하고 야구게임 프로젝트를 진행하면서 아쉬웠던 부분이나 마주쳤던 문제를 작성해보겠다.
react-router의 기본적인 원리는 window.history API
와 react의 context
를 이용해 구현했다. Router
컴포넌트 하위에 있는 Link
와 Route
컴포넌트들이 path에 관련된 정보를 context
로 받아와서 사용한다.
(codesandbox의 new window에서는 history가 안 쌓여서 뒤로가기가 되지 않는다.)
Link
와 Route
에서 사용할 주소값(상태)를 관리해주는 currentPath
라는 state를 만들어 놓고 Context.Provider
로 전해준다. 초기 셋팅을 useEffect
를 이용해 2가지 작업을 해준다.
새로고침해도 값을 유지해야되기 때문에 window.location.pathname
와 window.location.search
를 이용해서 url 경로를 설정하고 currentPath
상태를 setting 해준다.
뒤로가기를 위한 onpopstate
콜백함수를 설정해준다.
뒤로가는 페이지가 있을 경우에는 뒤로가는 path로 설정을 해주고 아닐 경우 default_page(/
)로 설정해준다.
const HistoryContext = createContext();
export const Router = ({ children }) => {
const DEFAULT_PATH = window.location.pathname;
const DEFAULT_QUERY = window.location.search;
const [currentPath, setCurrentPath] = useState();
useEffect(() => {
setCurrentPath(DEFAULT_PATH + DEFAULT_QUERY);
window.onpopstate = function (e) {
e.state ? setCurrentPath(e.state?.page.to) : setCurrentPath(DEFAULT_PATH);
};
}, []);
return (
<HistoryContext.Provider value={{ currentPath, setCurrentPath }}>
{children}
</HistoryContext.Provider>
);
};
Link
컴포넌트의 역할은 클릭했을 때 props.to
의 path값으로 이동시켜 주는 것이다.
setCurrentPath
를 이용해 상태를 변경시켜준다.pushState
를 해서 history에 push하면서 url path를 변경시켜준다. (현재 a
태그로 했지만 다른 태그를 사용해도 무관하다.)
export const Link = ({ to, children }) => {
const { currentPath, setCurrentPath } = useContext(HistoryContext);
const handleClick = (e) => {
e.preventDefault();
if (currentPath === to) return;
setCurrentPath(to);
window.history.pushState({ page: { to } }, to, to);
};
return (
<a href="" onClick={handleClick}>
{children}
</a>
);
};
라우트의 역할은 currentPath
값과 비교해 렌더링해주는 컴포넌트이다.
currentPath
값에 prop.path
가 포함된다면 렌더링한다.
exact
[boolean]: true이면 path값이 일치할 때 만 렌더링한다.export const Route = ({ children, exact, path }) => {
const { currentPath } = useContext(HistoryContext);
if (exact && currentPath !== path) return null;
if (currentPath?.includes(path)) return children;
else return null;
};
그냥 사용해도 너무 편하게 잘 만들어져 있지만 한번 구현해보는 과정도 재밌었다. 덕분에 history API
에 대해서도 알게 됐고 어떤 방식으로 구현이 되는지 알 수 있었다.
시간이 부족하기도 했고 잘 몰라서 해결하지 못한 문제들이 있다.
2,3루타 와 홈런을 구현하기 위해서 base주자들이 한칸씩만 이동하도록 설계를 했다.
transitionEnd
이벤트를 사용해 transitionEnd때 마다 한칸씩 이동하도록 했지만 이것 역시 맨처음만 애니메이션이 작동되고 그 후에 최종버전으로 setState가 됐다. (이부분은 아직 이해 불가능)아직 시도는 안해봤지만 모든 경우를 keyframes로 설정해주면 가능할 것 같다는 생각이든다.
setTimeout
혹은 setInterval
을 이용해 pitch하는 함수를 반복적으로 실행해주려고 했지만 계속 중첩되는 문제가 발생함 아직 어떻게 해결해야될지 고민을 제대로 못해봐서 아쉬움이 남는다.
useReducer
를 사용하면서 연관되어있는 2가지 이상의 상태는 어떻게 컨트롤하지? 라는 생각을 했다.
-> 나는 커스텀훅을 만들어서 그 훅을 이용했는데...
-> 관련된 상태들을 객체형태로 관리하면 해결되는 문제였다.
상태들이 워낙 많고 Context.Provider
를 계속 사용하다 보니 코드가 보기 어려워 진것 같다. context에 관련된 몇가지 고민들이 있었다.
어느 수준은 props로 내려주고 어떤 경우에 context
를 사용할지에 대한 고민이 계속된다.
나는 여러군데에서 동시에 다 사용하거나 너무 깊은 곳에서 사용하지 않으면 그냥 props로 내려줘도 괜찮다고 생각하는데 조금 더 큰 프로젝트를 다뤄보면서 배우게 될 것 같다.
context를 관련있는 상태별로 구별해야될까???
이 부분에서는 리렌더링 문제도 있기 때문에 그 상태의 변화로 인해서 변경되는 단위?로 묶어주면 된다고 생각한다.
2주 동안 제이슨과 프로젝트를 진행하면서 전체적으로는 여유로웠지만 진행할 때만큼은 집중해서 했다. 10시~6시까지만 집중해서 하는 것을 목표로 하루를 제외하고는 모두 그렇게 했던 것 같다.
일단 큰 장점은 프로젝트를 끝나고 내가 부족했던 부분이나 추가로 공부해보고 싶은 부분에 공부할 수 있는 시간을 얻을 수 있었다. 그 시간 동안 알고리즘이나 react에 관련된 것들을 공부할 수 있는 시간을 가졌다.
이번에 프로젝트를 진행하면서 백엔드분들과 많이 소통한 부분도 좋았다. 이번 프로젝트는 API에 대해 매일매일 소통하면서 수정이 필요한 부분을 계속 수정하면서 진행했다. 처음에는 당연히 완벽한 API 설계가 될 수 없으니 매일매일 조금씩 발전시키면서 완성시켰고 이 과정이 너무 재밌었다.
코딩테스트 2차에서 vanilla JS로 상태를 통한 렌더링을 하는 미션이 많이 주어지고 있는 것 같다. 그 부분을 학습해야겠다.
크.. 카일 라우터 넘나 멋짐다! 👍👍