Vite React useRouter Hook 만들기

임동현·2023년 7월 10일
0

History API 를 이용한 Router 구현하기
[클릭]

이전에는 History API 를 통해서 SPA Router 를 구현해 봤다.

window.history.pushState() 함수를 사용하여 새로운 브라우저 히스토리 항목을 추가하고, 이를 통해 URL을 변경하는 방식으로 페이지 이동을 처리하고 있습니다.

또한, 'popstate' 이벤트를 감지하고 이 이벤트를 통해 URL이 변경되면 적절한 컴포넌트를 렌더링하는 로직을 구현하고 있습니다. 이는 페이지를 새로 고칠 필요 없이 사용자에게 새로운 페이지를 보여줄 수 있게 해줍니다.

이렇게 구현하면 전통적인 다중 페이지 방식보다 더 빠르고 매끄러운 사용자 경험을 제공이 가능하다!

이번에는 React useRouter Hook 을 만들어 보려고 한다.

이번 Wanted onBoarding 을 하면서 다른사람의 코드를 바라보며 내것으로 만드는것도 중요하다고 생각이 들기에 참고를 해서 공부를 했습니다.

참고 [https://github.com/hayhyang/react-anatomy] 양식을 주셔서 정말 감사합니다. (https://github.com/hayhyang/react-anatomy)

main.tsx => App.tsx

'Router' , 'Routes' , 'Route' 와 같이 라우팅 관련 컴포넌트는 각각 다른 역할을 수행하게 됩니다.

  1. Router 컴포넌트: 이 컴포넌트는 애플리케이션의 라우팅 컨텍스트를 설정합니다. 이 컨텍스트는 현재의 경로를 추적하고 해당 상태를 업데이트 하는데 사용됩니다.
    'Router' 컴포넌트는 자식 컴포넌트에 현재 경로 상태와 그 상태를 변경하는데 함수를 제공합니다.
import { Dispatch, ReactNode, SetStateAction, createContext, useMemo, useState } from "react";

interface RouterProps {
    children: ReactNode;
}

export const RouterContext = createContext({
    path: window.location.pathname,
    setPath: (() => null) as Dispatch<SetStateAction<string>>,
});

const Router = ({ children }: RouterProps) => {
    const [path, setPath] = useState(window.location.pathname);

    const value = useMemo(() => ({ path, setPath }), [path, setPath]);

    return <RouterContext.Provider value={value}>{children}</RouterContext.Provider>;
};

export default Router;
  1. Routes 컴포넌트: Routes 는 자식 Route 컴포넌트를 반복하고 현재 경로와 일치하는 Route 를 렌더링 합니다.
import { ReactNode, useContext, ReactElement } from "react";
import { RouterContext } from "./Router";
import React from "react";

// RoutesProps  : Routes props 에 대한 타입을 정의

interface RoutesProps {
    children: ReactNode | ReactNode[];
}

const Routes = ({ children }: RoutesProps) => {
    const { path, setPath } = useContext(RouterContext);

    window.onpopstate = () => setPath(window.location.pathname);

    let matchedComponent = null;

//이 함수는 children props 에서 각 child 를 순회하며 , 각 child가 유효한 React 
// 요소인지 확인(React.isValidElement 하고 , 현재 경로 path 와 일치하는지 확인 
// 일치하는 경우 , 해당 child component prop을 mathedComponent 변수에 할당합니다.
    React.Children.forEach(children, (child) => {
        if (React.isValidElement(child) && path === (child as ReactElement).props.path) {
            console.log(
                "current",
                (child as ReactElement).props.path,
                (child as ReactElement).props.component
            );
            matchedComponent = (child as ReactElement).props.component;
        }
    });

    return matchedComponent;
};

export default Routes;
  1. Route 컴포넌트는 주로 라우팅 경로와 해당 경로에 렌더링 될 컴포넌트를 매핑 하는데 사용됩니다.
import { ReactNode } from "react";

interface RouteProps {
    path: string;
    component: ReactNode;
}
const Route = (props: RouteProps) => null;

export default Route;

Route 컴포넌트는 실제로 아무것도 렌더링 하지 않습니다. 이 컴포넌트의 주 목적은 , 라우팅 로직에 필요한 path 와 component props 를 제공하는 것 입니다.
'Routes' 컴포넌트는 'Route' 컴포넌트의 props 를 사용하여 경로와 컴포넌트를 매핑하고 , 현재 경로에 해당하는 컴포넌트 렌더링을 합니다 .따라서 , 라우팅 로직에 대한 정보를 제공하는 역할을 하고 있습니다.

Route 에 제공된 path , component 에 매핑을 하고

Hook 폴더에 useRouter.tsx

const useRouter = () => {
    const push = (path: string) => {
        history.pushState(null, "", path);
        const popStateEvent = new PopStateEvent("popstate");
        dispatchEvent(popStateEvent);
    };
    return { push };
};

export default useRouter;

useRouter 커스텀 훅은 웹페이지의 현재 URL 을 바꾸는 기능을 제공합니다.

상수 push 함수를 통해서 path 라는 문자열을 인수를 받아서 , 그 값을 새로운 URL 로 사용합니다. 이 함수는 이전에 History API 를 사용했던 history.pushState() 를 사용하여 , 첫 번째 인수로는 상태 객체를 받고 , 두번째 인수로는 제목을 받지만 , 이 예제에서는 둘다 사용하지 않고 세 번째인 path 만 사용하여 새로운 url 을 받습니다.

new PopStateEvent("popstate") 와 dispatchEvent(popStateEvent) 이 두줄은 브라우저에 "popstate" 라는 이벤트가 발생했다는 신호를 내보냅니다. 이 이벤트는 브라우저의 히스토리 엔트리가 변경될 때 발생하는 이벤트입니다.

useRouter 훅을 완성 시켰으므로 , push 함수를 반환하고 이 훅을 사용하는 컴포넌트는 이 함수를 호출하여 페이지를 이동시킬수 있습니다.

import reactLogo from "../assets/react.svg";
import viteLogo from "/vite.svg";
import "../App.css";
import useRouter from "../hook/useRouter";

const Main = () => {
    const { push } = useRouter();

    const ClickFly = () => {
        push("/about");
    };
    return (
        <>
            <div>
                <a href="https://vitejs.dev" target="_blank">
                    <img src={viteLogo} className="logo" alt="Vite logo" />
                </a>
                <a href="https://react.dev" target="_blank">
                    <img src={reactLogo} className="logo react" alt="React logo" />
                </a>
            </div>
            <h1>Vite + React Main Home</h1>
            <button onClick={ClickFly}>About Page </button>
            <p>Root</p>
            <p className="read-the-docs">Click on the Vite and React logos to learn more</p>
        </>
    );
};

export default Main;

구조분해 할당을 통해서

const {push} = useRouter(); 를 상수로 선언후 클릭함수를 만들어서 구현을 완성 시켰습니다

profile
프론트엔드 공부중

0개의 댓글