一. react-router
介绍
react-router
是React
框架配套的路由库,支持配置路由与组件的映射关系。
代码示例如下
javascript
function Home() {
return <h1>Home</h1>
}
function Account() {
return <h1>Account</h1>
}
function App() {
return (
<BrowserRouter>
<div>
<ul>
<li>
<Link to='/'>home</Link>
</li>
<li>
<Link to='/account'>account</Link>
</li>
</ul>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/account' element={<Account />} />d
</Routes>
</div>
</BrowserRouter>
)
}
二. 实现react-router
react-router
核心原理是采用路由劫持,即监听window
的popstate
事件,当页面路由变更时会触发popstate
事件,执行路由组件渲染逻辑。
2.1 BrowserRouter
BrowserRouter
组件的主要逻辑是负责初始化路由劫持逻辑,当路由变更时触发组件更新渲染。触发组件更新渲染主要通过React
的useSyncExternalStore
方法实现,在往期文章手写React useSyncExternalStore,理解useSyncExternalStore原理有介绍其实现原理,本文不再赘述。
javascript
function BrowserRouter({ children }) {
const historyRef = useRef()
if (historyRef.current === null) historyRef.current = createBrowserHistory()
const history = historyRef.current
const { action, location } = useSyncExternalStore(
history.subscribe,
history.getSnapshot,
)
return (
<Router location={location} navigator={history} navigationType={action}>
{children}
</Router>
)
}
2.1.1 createBrowserHistory
创建自定义history
对象实例。listener
变量用于记录触发组件更新渲染的方法,当路由变更时,会调用该方法触发更新渲染。
javascript
const Action = {
Pop: 'Pop',
Push: 'Push',
Replace: 'Replace',
}
function createBrowserHistory() {
let action = Action.Pop
let listener = null
const handlePop = () => {}
const history = {
get action() {
return action
},
get location() {
const { pathname, search, hash } = window.location
return {
pathname,
search,
hash,
}
},
push: to => {
action = Action.Push
window.history.pushState(null, '', to)
listener()
},
subscribe: callback => {
listener = callback
window.addEventListener('popstate', handlePop)
return () => {
window.removeEventListener('popstate', handlePop)
listener = null
}
},
getSnapshot: () => {
return {
action: history.action,
location: history.location,
}
},
}
return history
}
2.1.2 Router
Router
组件逻辑比较简单,主要是添加两个Context
组件包括子组件,便于子组件通过useContext
拿到传递的属性值。
javascript
const NavigationContext = createContext(null)
const LocationContext = createContext(null)
function Router({ location, navigator, navigationType, children }) {
return (
<NavigationContext.Provider value={{ navigator }}>
<LocationContext.Provider value={{ location, navigationType }}>
{children}
</LocationContext.Provider>
</NavigationContext.Provider>
)
}
2.2 Link
Link
组件负责渲染a
标签,当触发点击事件时会调用自定义history.push
方法变更页面路由,然后触发组件更新渲染。
javascript
function Link({ to, children }) {
const { navigator } = useContext(NavigationContext)
return (
<a
onClick={() => {
navigator.push(to)
}}
>
{children}
</a>
)
}
2.3 Routes
Routes
组件负责解析route
配置,并根据当前页面路由匹配需要渲染的route
配置。
javascript
function useRoutes(routes) {
const location = useLocation()
// 获取匹配的路由
const matches = matchRoutes(routes, location)
return matches.map((match, index) => (
<RouteContext.Provider key={index} value={{ match }}>
{match.route.element}
</RouteContext.Provider>
))
}
// 遍历Route组件创建其对应的route配置
function createRoutesFromChildren(children) {
return children.map(element => ({
path: element.props.path,
element: element.props.element,
}))
}
function Routes({ children }) {
return useRoutes(createRoutesFromChildren(children))
}
2.4 Route
Route
组件没什么业务逻辑,其作用类似React.Fragment
组件,主要是为Routes
组件服务的,便于转换成对应的route
配置。
javascript
function Route({ path, element }) {
return <></>
}
三. 总结
react-router
核心原理是采用路由劫持,当页面路由变更时触发组件更新渲染,然后获取当前页面路由对应的组件进行渲染。代码仓库
创作不易,如果文章对你有帮助,那点个小小的赞吧,你的支持是我持续更新的动力!