根据运行环境安装react-router-dom,它依赖react-router,安装会自动安装react-router。
react-router 中奉行一切皆组件的思想。
关于router的具体使用和API可以查看相关文档事例。
这里手写的代码都是通过hook来实现的,如果想看基于类组件的实现,可以去看官方的源码
react-router的源码中,BrowserRouter的实现,是创建了一个顶层的history、location、match等参数,通过Context.provider将状态向下传递,通过中间层组件Router实现核心功能。
这里将Router的实现一并揉进了BrowserRouter中实现,简化了一些额外的处理。
BrowserRouter主要做了两件事:
import React, { useContext, useState, useEffect } from 'react'
import { createBrowserHistory } from 'history'
const RouterContext = React.createContext()
export function BrowserRouter({ children }) {
// 1. 创建history,location等对象,使用Context向下传递,
const history = createBrowserHistory()
const [location, setLocation] = useState(history.location)
// 2. 监听location的变化,重绘
useEffect(() => {
const clearListen = history.listen(location => {
console.log('location', location)
setLocation(location)
})
return () => {
if (clearListen) {
clearListen()
}
}
}, [location])
return (
<RouterContext.Provider
value={{
history,
location
}}>
{children}
</RouterContext.Provider>
)
}
通过元素检查,会发现Link最终返回的是一个a标签
源码中对ref 做了forwardRef()的处理,这里我只做了简单的功能实现
export function Link({ to, children }) {
const { history } = useContext(RouterContext)
const handlerClick = (e, history) => {
// 阻止默认跳转
e.preventDefault()
history.push(to)
}
return (
<a
href={to}
onClick={e => handlerClick(e, history)}>
{children}
</a>
)
}
Route 主要做路由匹配和渲染,匹配规则是匹配URL的开头,而不是整个URL,这里做了简单处理,细节可以深入去读源码。
Route 渲染规则是children > component > render
, 读源码会发现,在未匹配时,如果我们设置了函数类型的children,它是无条件渲染的。
export function Route(props) {
const context = useContext(RouterContext)
// 将路由信息当做参数传递给组件
const propsMap = { ...context }
// 1. 判断路径是否匹配 ,不匹配,直接返回null
const isMatch = props.path === context.location.pathname
// 2. 如果匹配,根据渲染优先级 children > component > render 进行渲染
function matchedRenderCmp(props) {
const { component, children, render } = props
console.log(children)
return children
? typeof children === 'function'
? children(propsMap)
: children
: component
? React.createElement(component, propsMap)
: render
? render(propsMap)
: null
}
// 3. 未匹配,如果props中有children并且是一个函数,则render children
function noMatchRenderCmp(props) {
const { children } = props
return (children && typeof children === 'function') ? children(propsMap) : null
}
return isMatch ? matchedRenderCmp(props) : noMatchRenderCmp(props)
}
一个简单的路由就写好了,现在上测试代码
import React, { Component } from
// 引入自己写的ZRouterDom
import {
BrowserRouter as Router,
Route,
Link
} from '../ZRouterDom'
import LoginPage from '../pages/LoginPage'
import HomePage from '../pages/HomePage'
export default class MyRouterPage extends Component {
render() {
return (
<Router>
<h1>MyRouterPage</h1>
<nav>
<Link to="/login"> login </Link>
<Link to="/"> home </Link>
</nav>
<Route path="/login"
render={()=><div>我是render</div>}
component={LoginPage}
children={<div>我是children1</div>}
></Route>
<Route path="/" component={HomePage} />
<Route path="/aaa" children={()=><div>我是未匹配的children</div>}></Route>
</Router>
)
}
}
我们可以进行下一步,看看import { createBrowserHistory } from 'history'
做了哪些事?