React Custom Router

May 05, 2021

0. 이글을 작성하는 이유?

  • React-Router는 어떻게 작동할까?

1. SPA란?

  • 새로고침은 UX적으로 좋지않다.
  • 새로운 페이지를 렌더링 하지 않고, 부분적으로 렌더링을 하자 (react)
  • URL도 변경되면서, '앞으로가기/뒤로가기'가 동작되면 좋겠다 (react-router)

2. History API

  • BOM의 History는 사용자의 방문기록 및 '앞/뒤(으)로 가기' 같은 API를 제공한다.

  • 주요 메소드

    // 뒤로 가기
    history.back()
    
    // 앞으로 가기
    history.forward()
    
    // history stack을 기준으로 특정 지점으로 이동할 수 있다.
    history.go(-1) // === history.back()
    history.go(1) // === history.forward()
    
    // history의 state(세션기록에) 해당 state를 넣으며 url을 변경해준다
    // 하지만, url로 변경하지만 탐색하지 않는다(단순히 url 변경)
    history.pushState(state, title, url)

3. Custom Router

  1. 구성요소

    1. Route

      • popstate를 통해 navigation 변화가 발생하면 event.state값을 받아 다시 렌더링 하는 컴포넌트
    2. Link

      • pushState를 활용해 history를 저장하는 컴포넌트
    3. Router

      • Link와 Route컴포넌트 부모로써, Context API를 활용
  2. 코드

    1. RouterContext

      • Context API를 활용하며, popstate이벤트가 생기면 리렌더링
    export const HistoryContext = React.createContext({
        history: window.history,
    })
    
    export const Router = ({ routeList, children }) => {
        const [routes] = useState(() =>
            Object.keys(routeList).map((key) => routeList[key].path)
        )
        const [currentPath, setCurrentPath] = useState(
            () => window.location.pathname
        )
    
        const is404 = routes.indexOf(currentPath) === -1
        const handlePopStateEvent = useCallback(({ state }) => {
            if (state && state.to) {
                setCurrentPath(state.to)
            }
        }, [])
    
        useEffect(() => {
            window.addEventListener('popstate', handlePopStateEvent, false)
            return () => {
                window.removeEventListener(
                    'popstate',
                    handlePopStateEvent,
                    false
                )
            }
        }, [])
    
        return (
            <HistoryContext.Provider
                value={{
                    currentPath,
                    setCurrentPath,
                }}
            >
                {is404 ? <NotFound /> : children}
            </HistoryContext.Provider>
        )
    }
    1. Route
    export const Route = ({ path, children, ...props }) => {
        const { currentPath } = useContext(HistoryContext)
        if (path !== currentPath) {
            return null
        }
        return <div {...props}>{children}</div>
    }
    1. Link
    export const Link = ({ to, children, ...props }) => {
        const { currentPath, setCurrentPath } = useContext(HistoryContext)
        const handleLinkClick = useCallback(
            (e) => {
                e.preventDefault()
                if (currentPath === to) {
                    return
                }
                history.pushState({ to }, '', to)
                setCurrentPath(to)
            },
            [currentPath]
        )
        return (
            <a {...props} onClick={handleLinkClick}>
                {children}
            </a>
        )
    }
    1. NotFound
    export const NotFound = () => {
        return (
            <div>
                <p>404 - NotFound</p>
                <Link to={routes.home.path}>Back to home</Link>
            </div>
        )
    }

출처


© 2023, Customized by Joon