[리액트, js] 리액트 라우터없이 라우터 만들기

·2022년 12월 27일
0

공부한 것

목록 보기
45/58

예제

영화 목록을 보여주는 페이지가 있고, 목록 중 하나를 클릭하면 해당 영화의 디테일 페이지를 보여주는 사이트

  • 영화 목록 컴포넌트 : MovieList
  • 영화 목록 아이템 컴포넌트 : Movie
  • 영화 디테일 페이지 컴포넌트 : MovieDetail

App.js

function App() {
  const movieId = useContext(MovieContext);

  return (
    <>
      <Route path={"/"}>
        <MovieList />
      </Route>
      <Route path={`/movie/${movieId}`}>
        <MovieDetail />
      </Route>
    </>
  );
}

location 객체

라우터를 만드는 게 목표니까 Route 컴포넌트를 만들기 전 현재 위치에 대한 정보가 들어있는 location 객체에 대해 알아야 한다. 여기서 중요한 부분은 pathname이다.

Window.location 프로퍼티에 접근하면 읽기 전용인 Location 오브젝트를 얻어올 수 있습니다. 이는 현재 도큐먼트의 로케이션에 대한 정보를 담고 있습니다.
[MDN - Window.location]

이미지 출처

이미지 출처

<Route>

먼저 router 폴더를 만들고 Route.js를 만든다.

const Route = ({ path, children }) => {
  return window.location.pathname === path ? <>{children}</> : null;
};

export Route;

props로 주어진 path현재 문서의 pathname과 같을 때 하위 컴포넌트들을 출력한다.

function Link({ href, children }) {
  const onClick = (e) => {
    //새로고침 막기
    e.preventDefault();
    //url 바꿔주기
    window.history.pushState({}, "", href);
  };

  return (
    <a href={href} onClick={onClick}>
      {children}
    </a>
  );
}

export { Link };

a 태그를 대신할 Link 컴포넌트를 만든다. a 태그는 href 경로의 파일을 다운로드하고 새로고침하는 게 기본 동작이기 때문에 e.preventDefault로 새로고침을 막아준다.

History.pushState()를 사용하면 페이지를 리로드 하지 않고 페이지 주소(url)만 변경할 수 있다.

  • HTML 문서에서, history.pushState() 메서드는 브라우저의 세션 기록 스택에 상태를 추가합니다.
  • 어떤 면에선 pushState()window.location = "#foo"가 비슷합니다. 둘 다 새로운 세션 기록 항목을 생성하고 활성화하기 때문입니다. 그러나 pushState()에는 몇 가지 장점이 있습니다.
    MDN - History.pushState()

pushstate()는 현재의 주소를 다른 것으로 변경하면서 페이지는 그대로 유지해주기 때문에 페이지가 전환, 갱신되는 것을 방지하면서 동시에 주소창의 url이 변경된다.

<Route> 리렌더링되게 하기

popstate 이벤트 객체 만들기

popstate 이벤트는 history가 바뀌면 발생한다. 하지만 pushState()로 변경된 경우는 발생하지 않는다.

History.pushstate
페이지 이동없이 주소만 바꿔준다. (브라우저의 뒤로가기 버튼이 활성화 된다.)
브라우저 페이지를 이동하게되면 window.onpopstate라는 이벤트가 발생하는데, pushState를 했을 때는 popstate이벤트가 발생하지 않고, 뒤/앞으로 가기를 클릭했을 때 popstate이벤트가 발생하게 된다.
=> pushState와 popstate를 이용해 SPA의 페이지 전환을 구현할 수 있다. history.pushState 화면전환 / 현재 페이지 주소창 url변경하기

function Link({ href, children }) {
  const onClick = (e) => {
    e.preventDefault();
    window.history.pushState({}, "", href);
    //popstate 이벤트 생성
    const navEvent = new PopStateEvent("popstate");
    window.dispatchEvent(navEvent);
  };

  return (
    <a href={href} onClick={onClick}>
      {children}
    </a>
  );
}

popstate 이벤트 객체를 새롭게 생성하고 윈도우에 붙여준다. 이벤트를 발생시켜야 한다는 건 알겠는데, 왜 새로 만들어줘야 하는지 계속 찾아봤는데 잘 모르겠다... 이 부분을 생략하면 popstate 이벤트는 발생하지 않는다...

popstate 이벤트로 location.pathname 상태 업데이트하기

Window: popstate event
사용자가 세션 기록을 탐색하는 동안 활성 기록 항목이 변경되면 인터페이스 의 popstate이벤트가 시작됩니다. Window현재 기록 항목을 사용자가 방문한 마지막 페이지의 항목으로 변경하거나 history.pushState()기록 항목을 기록 스택에 추가하는 데 사용된 경우 해당 기록 항목이 대신 사용됩니다.
MDN - Window: popstate event

popstate 이벤트가 발생하면 아까 pushstate() 한 url을 사용한다.

function Route({ path, children }) {
  const [currentPath, setCurrentPath] = useState(window.location.pathname);

  useEffect(() => {
    const onLocationChange = () => {
      setCurrentPath(window.location.pathname);
    };

    window.addEventListener("popstate", onLocationChange);

    return () => {
      window.removeEventListener("popstate", onLocationChange);
    };
  });

  return currentPath === path ? <>{children}</> : null;
}

Route 컴포넌트가 리렌더링이 되어야 해당 라우터의 자식 컴포넌트들도 리렌더링이 된다. 그러면 사용자는 페이지가 이동하는 것처럼 보이게 되는 것이다.

Route 컴포넌트가 리렌더링 되기 위해 location.pathname을 상태로 관리한다. 상태는 popstate 이벤트가 발생할 때마다의 location.pathname으로 업데이트된다. (이게 이동하고 싶은 주소니까)

그렇게 popstate 이벤트가 발생할 때마다 리렌더링되는 Route 컴포넌트는 currentPath===path를 판단하고 우리에게 필요한 페이지만 렌더링 되는 것이다.

하고나서..

pushstate, popstate가 뭔지 엄청 찾아봤다.. 휴..
지금 방법으로는 동적 라우팅은 안 되는 것 같다. 참고한 글의 내용도 여기까지라서 흠.. context api를 사용하면 될 것 같긴 한데 /movie/:id 이렇게 할 수 있는 방법을 생각해 봐야겠다.

참고자료

location

history

0개의 댓글