react-router原理及代码实现

41 阅读1分钟

基本原理

监听path/url的变化,根据path和component之间的关系,触发组件进行mount/unmount,同时使用context注入上下文

使用案例

const {BrowserRouter,Route, Routes} = require('react-route-dom')
 function App(){
    return <BrowserRouter>
        <header>
            <a href='./'>首页</a>
            <a href='./list'>列表</a>
            <a href='./about'>关于</a>
            <a href='./hot'>热点</a>
        </header>
        <Routes>
            <Route path="/list" element={<div>列表页面</div>}></Route>
            <Route path="/about" element={<div>关于页面</div>}></Route>
            <Route path="/list" element={<div>热点页面</div>}></Route>
        </Routes>
    </BrowserRouter>
} 

源码实现

import React,{useMemo,useRef,useContext } from 'react'
const NavigationContext = createContext({})
const LocationContext = createContext({})

export function BrowserRouter({children}){
    let historyRef = useRef()
    if(historyRef.current == null){
        history.current = createBrowserHistory()
    }
    let history = historyRef.current
    let [state,setState] = useState({
        action: history.action,
        location = history.location
    })
    useLayoutEffect(()=>history.listen(setState),[history])
    return <Router 
        children = {children}
        location = {state.location}
        navigator = {history}
        navigationType = {state.action}
    />
}
function Router({children,location: locationProp,navigator}){
    const navigationContext = useMemo(()=>({navigator}),[navigator])
    const locationContext = useMemo(()=>({location: locationProp},[]locationProp))
    return <NavigatonContext.Provider value = {navigationContext}>
        <LocationContext.Provider value = {locationContext} children = {children}></LocationContext.Provider>
    </NavigatonContext>
}
function useLocation(){
    return useContext(LocationContext).location
}
function useNavigate(){
    return useContext(NavigationContext).navigator
}
export const Routes = ({childrem})=>{
    useRoutes(createRouterFromChildren(children))
}
export useRoutes(routes){
    let location = useLocation()
    let currentPath = location.pathname || '/'
    let route = null
    routes.forEach(({path,element})=>{
        let match = currentPath.match(new RegExp(`^${path}`))
        if(match){
            route = element
        }
    })
    return route
}
export const createRouterFromChildren = (children)=>{
    let routes = []
    React.Children.forEach(children,(node)=>{
        let route = {
            element: node.props.element,
            path: node.props.path
        }
    })
    if(node.props.children){
        route.children = createRouterFromChildren(node.props.children)
    }
    routes.push(route)
}