React.js 路由
课程目标
- 掌握 react-router 使⽤⽅法;
- 能使⽤ react-router 设计开发 react 应⽤;
- 理解 react-router 关键源码实现。
- 理解 react-router 和 vue-router 的实现差异,针对⾯试提出的问题能举⼀反三;
课程大纲
- react-router 使⽤详解;
- 从0到1搭建⼀个基于 react-router 的应⽤;
- react-router 关键源码解析;
- 对⽐ react-router 和 vue-router的差异;
知识要点
react-router 基本使用
路由配置
jsx 用法:
1 | import { render } from "react-dom"; |
config + hooks 用法:
1 | import { render } from 'react-dom' |
页面跳转
jsx 用法:
1 | <Link to="/">To</Link> |
hooks 用法:
1 | const location = useLocation() |
核心源码解析
我们经常使⽤的 BrowserRouter 和 HashRouter 主要依赖三个包:react-router-dom、react-router、 history。
react-router 提供 react 路由的核⼼实现,是跨平台的。
react-router-dom 提供了路由在 web 端的具体实现,与之同级的还有 react-router-native,提供 react-native 端的路由实现。
history 是⼀个对浏览器 history 操作封装,抹平浏览器 history 和 hash 的操作差异,提供统⼀的 location 对象给 react-router-dom 使⽤。
下⾯从最简单的例⼦,进⼊ react-router 源码解析:
1 | ReactDOM.render( |
BrowserRouter 实现:
1 | export function BrowserRouter({ |
BrowserRouter 包装了
Router实现:
1 | export function Router({ |
Router 处理了 history 对象,将其⽤ NavigationContext 包裹,使得下层⼦组件都可以访问到这个 history。
Routes实现:
1 | export function Routes({ |
Routes 通过 Route ⼦组件⽣成路由列表,通过 location 中的 pathname 匹配组件并渲染。
通过以上代码,我们基本理解了 react-router 如何感知 history 中的 pathname 变化,并渲染对应组件。
但我们具体是如何操作 history 变化的呢?
我们在回到最上⾯的 createBrowserHistory 和 history.listen ⽅法,看看 history 对象是怎么被创建和改变的:
1 | export function createBrowserHistory( |
createBrowserHistory 创建了⼀个标准的 history 对象,以及对 history 对象操作的各⽅法,且操作变更后,通过 listen ⽅法将变更结果回调给外部。
getIndexAndLocation实现:
1 | function getIndexAndLocation(): [number, Location] { |
push 实现:
1 | function push(to: To, state?: any) { |
问: 我们在代码中都是 const location = useLocation(); location.push("/")
这样的⽅式使⽤ push
的,那上⾯这个 push
⽅法到底是怎么跟 useLocation
关联的呢?
还记得 Router 中有这么⼀段代码吗?const LocationContext = React.createContext<LocationContextObject>(null!);
我们的history对象创建后会被Router注⼊进⼀个LocationContext的全局上下⽂中。 useLocation实际就是包裹了这个上下⽂对象。
1 | export function useLocation(): Location { |
总结⼀下,BrowserRouter 核⼼实现包含三部分:
- 创建 history 对象,提供对浏览器 history 对象的操作。
- 创建 Router 组件,将创建好的 history 对象注⼊全局上下⽂。
- Routes 组件,遍历⼦组件⽣成路由表,根据当前全局上下⽂ history 对象中的 pathname 匹配当前激活的组件并渲染。
HashRouter 和 BrowserRouter 原理类似,只是监听的浏览器原⽣ history 从 pathname 变为 hash。
react-router 和 vue-router 的差异
路由类型
React:
- browserRouter
- hashRouter
- memoryRouter
Vue: - history
- hash
- abstract
memoryRouter 和 abstract 作⽤类似,都是在不⽀持浏览器的环境中充当 fallback。
使用方法
- 路由拦截的实现不同
- vue router 提供了全局守卫、路由守卫、组件守卫供我们实现路由拦截。
- react router 没有提供类似 vue router 的守卫供我们使⽤,不过我们可以在组件渲染过程中⾃⼰实现路由拦截。如果是类组件,我们可以在
componentWillMount
或者getDerivedStateFromProps
中通过props.history
实现路由拦截;如果是函数式组件,在函数⽅法中通过props.history
或者useHistory
返回的 history 对象实现路由拦截。
实现差异
- hash 模式的实现不同 (新版本已相同)
- react router 的 hash 模式,是基于
window.location.hash(window.location.replace)
和hashchange
实现的。当通过push
⽅式跳转⻚⾯时,直接修改window.location.hash
,然后渲染⻚⾯; 当通过replace
⽅式跳转⻚⾯时,会先构建⼀个修改hash
以后的临时url
,然后使⽤这个临时url
通过window.location.replace
的⽅式替换当前url
,然后渲染⻚⾯;当激活历史记录导致hash
发⽣变化时,触发hashchange
事件,重新渲染⻚⾯。 - vue router 的 hash 模式,是先通过
pushState(replaceState)
在浏览器中新增(修改)历史记录,然后渲染⻚⾯。 当激活某个历史记录时,触发popstate
事件,重新渲染⻚⾯。 如果浏览器不⽀持pushState
,才会使⽤window.location.hash(window.location.replace)
和hashchange
实现。
- react router 的 hash 模式,是基于
- history 模式不⽀持
pushState
的处理⽅式不同- 使⽤ react router 时,如果 history 模式下 不⽀持 pushState,会通过重新加载⻚⾯ (
window.location.href = href
) 的⽅式实现⻚⾯跳转。 - 使⽤ vue router 时,如果 history 模式下不⽀持 pushState,会根据
fallback
配置项来进⾏下⼀步处理。 如果fallback
为true
, 回退到 hash 模式;如果fallback
为false
, 通过重新加载⻚⾯的⽅式实现⻚⾯跳转。
- 使⽤ react router 时,如果 history 模式下 不⽀持 pushState,会通过重新加载⻚⾯ (
- 懒加载实现过程不同
- vue router 在路由懒加载过程中,会先去获取懒加载⻚⾯对应的 js ⽂件。等懒加载⻚⾯对应的 js ⽂件加载并执⾏完毕,才会开始渲染懒加载⻚⾯。
- react router 在路由懒加载过程中,会先去获取懒加载⻚⾯对应的 js ⽂件,然后渲染 loading ⻚⾯。等懒加载⻚⾯对应的 js⽂ 件加载并执⾏完毕,触发更新,渲染懒加载⻚⾯。
补充知识点
react-router 的登录校验实现思路
- 登录信息全局状态
- 定义
Auth
组件判断是否登录
1 | import { createContext, ReactNode, useContext, useEffect, useState } from 'react' |
react-router 懒加载的实现思路
React.lazy()
- dynamic
import()
React.Suspense
1 | const Account = React.lazy(() => import("./pages/Account")); |
在 react-router v6 中如何实现离开页面前确认
基于 history 的 block 实现
1 | import { useCallback, useContext, useEffect, useState } from 'react'; |
如何在服务端处理 react-router
<StaticRouter>
react-router 有哪些路由类型,及其实现原理和差异
为什么要分 react-router、react-router-dom,react-router-native 这样实现的好处是什么
核心实现与平台实现分开,更好的实现跨平台。