流程图
路由原理
- 有两种实现方法
- hashRouter 利用hash实现路由切换
- BrowserRouter 实现h5Api实现路由切换
- 相同点
- 都是创建一个ref对象 使用History库来创建 createHashHistory 对象和 createBrowserHistory对象 可以获取到history
- 存储history的action和location 监听history变化 改变存储状态内的location的值
- 返回一个Router对象 children 子元素 location是当前场景 navigationType 导航状态 navigator 导航(历史对象)
- 不同点
- BrowserRouter是根据H5(window.history)提供的Api来生成的
- hashRouter是自己模拟浏览器历史对象 根据hash的pushState来压入历史对象 监听window.onpopstate来监听变化来监听
createHashHistory的实现
- 创建一个历史栈 用来模拟浏览器历史栈 historyStack
- 定义历史栈默认指针 index
- 定义默认动作 action
- 定义当前路径状态 state
- 定义监听函数的数组 listeners
- 定义一个监听函数 返回一个取消监听的函数 listen
- go函数 改变action 从历史栈中找到对应的location对象 赋值给widnow.location
- goBack go(-1)
- goForword go(1)
- push方法 改变action 兼容pathname的对象写法 把pathname赋值给window.location.hash
- 监听hashchange事件 获取当前路由 添加到history对象中 并派发listeners的所有对象执行
createBrowserHistory的实现
- 使用window自带的history对象
- 定义监听函数的数组 listeners
- 定义一个监听函数 返回一个取消监听的函数 listen
- 监听popState事件
- push 是调用history的pushState
- notify 获取最新状态 添加到history对象中 并派发listeners的所有对象执行
HashRouter的实现
- 创建一个ref对象
- 给ref.current绑定上createHashHistory实现的history
- 定义一个history的useState 方便history数据更新的时候更新视图
- useLayoutEffect来监听history.listen函数 传入history.location和history.action 然后setState
- 返回一个Router组件
Router组件
- 获取导航上下文的内容
- 获取路径上下文的内容
- 并把Router的子节点放到路径上下文的子节点内
Routes
[{
path:'/home',
element:element节点
}]
- 把这个路由表传入useRoutes 根据路由表渲染真正的组件
useRoutes
- 获取当前的路由对象 useLocation
- 获取当前的路径字符串
- 用当前的地址栏中的路径和路由进行匹配
- 打平分支
- 根据分数进行路由排序
- 如果分数一样,按照分数倒序排序
- 如果分数不一样 就对比索引
- 如果级别数量相等,并且父亲都 一样,说是他们是兄弟
- 如果是兄弟的话,那和比索引,索引越小级别越高,索引越大,级别越低
- 如果不是兄弟,那就认为相等的
- 按照排序完的顺序依次进行匹配 如果匹配上了 直接退出循环,不再进行后续匹配
- 拿到当前地址栏的路径 根据/分割 跟routesMeta的路径进行分段匹配
- 匹配成功了就把params追加上
- 把所有匹配上的路由返回去 如果是二级路由 其实第一级路由也会返回 所以最后要用reduceRight来输出
- 渲染匹配的节点
- 渲染结果是从右到左的 是因为左边的是父级路由 最右边的是要渲染的节点
- 给路由上下文中增加outlet matchers
useLocation
Route 是一个空对象的语法糖
useNavigate
- 获取导航上下文对象中的navigator 也就是history 调用push方法即可
- 因为用了useMemo 所以调用的时候用了useCallback
- 返回useCallback(to)
Navigate
- 在useLayoutEffect中使用useNavigate
useParams
- 获取路由上下文中的matches
- 获取最后一项routeMatch
- 如果存在 就返回这个的params 如果没有就返回一个空对象
outlet
link
- 调用useNavigate 获取history
- 定义一个a标签 调用history.to方法 并把入参传入进行路由跳转
Navlink
- 在link的基础上加了一层判断
- 可传入className和style
- 要兼容className和style的函数写法
- 定义一个变量 如果目标路径和当前路径相同 或者是 当前路径的子路径且没有end结束符 为true 作为入参传到到className的函数体内