文章简介
本文主要介绍:
新版本路由和老版本的差异,使用区别,API 区别。 新版本路由组件 Router ,Routes ,和 Route 的原理。 Outlet 组件原理。 useRoutes 原理。
目录
一,react路由6和路由5的差异
1 路由5中的Switch与路由6中的Routes
2 嵌套路由的升级
3 路由状态和页面跳转
状态获取
路由跳转
动态路由
url参数信息获取
官方文档
二 原理分析
1 新版 Route 设计
2 外层容器,更新源泉 BrowserRouter | HashRouter | Router
老版本的 BrowserRouter
新版本的 BrowserRouter
新版本 Router
3 原理深入,Routes 和 branch 概念
Routes 和 useRoutes
useRoute
路由状态传递
三 v5 和 v6 区别总结
组件层面上:
使用层面上:
原理层面上:
一,react路由6和路由5的差异
?
1 路由5中的Switch与路由6中的Routes
新版本的 router 没有?Switch ?组件,取而代之的是 Routes ,但是在功能上?Routes ?是核心的,起到了不可或缺的作用。老版本的 route 可以独立使用,新版本的 route 必须配合 Routes 使用。
在新版的 router 中,已经没有匹配唯一路由的?Switch ?组件,取而代之的是?Routes ?组件,但是我们不能把 Routes 作为 Switch 的代替品。
因为在新的架构中 ,Routes 充当了很重要的角色,在?react-router路由原理?文章中,曾介绍到 Switch 可以根据当前的路由 path ,匹配唯一的 Route 组件加以渲染。
但是 Switch 本身是可以被丢弃不用的,但是在新版的路由中, Routes 充当了举足轻重的作用。
比如在 v5 中可以不用 Switch 直接用 Route,但是在 v6 中使用 Route ,外层必须加上 Routes 组件,也就是 Routes -> Route 的组合。
如果 Route 外层没有 Routes ,会报出错误。
2 嵌套路由的升级
新版本路由引入 Outlet 占位功能,可以更方便的配置路由结构,不需要像老版本路由那样,子路由配置在具体的业务组件中,这样更加清晰,灵活。
对于新版本的路由,嵌套路由结构会更加清晰.
比如在老版本的路由中,配置二级路由,需要在业务组件中配置,我们需要在?Children ?组件中进行二级路由的配置。
但是在 路由6 中,对于配置子路由进行了提升,可以在子路由直接写在 父组件里。
Container 内部运用了 v6 Router 中的?Outlet ?。
而 Outlet 才是真正渲染子路由的地方。
这里的 Outlet 更像是一张身份卡,证明了这个就是真正的路由组件要挂载的地方,而且不受到组件层级的影响。
这种方式更加清晰,灵活,能够把组件渲染到子组件树的任何节点上。
3 路由状态和页面跳转
状态获取
对于路由状态 location 的获取 ,可以用自定义 hooks 中?useLocation ?。location 里面保存了 hash | key | pathname | search | state 等状态。
路由跳转
新版路由提供了?useNavigate ?,实现路由的跳转。
动态路由
新版路由里面实现动态路由,也变得很灵活,可以通过 useParams 来获取 url 上的动态路由信息。比如如下
url参数信息获取
新版路由提供?useSearchParams ?可以获取?|?设置?url 参数
官方文档
v6 还提供了一些其他功能的 hooks ,这里就不一一讲了,有兴趣的同学可以看一下官方文档,
Docs Home v6.3.0 | React Router
二 原理分析
上述介绍了从使用上,v5 和 v6 版本路由的区别。接下来,我们重点看一下新版 Route 的原理。以及和老版本有什么区别。
1 新版 Route 设计
老版本的路由,核心的组件是 **Route **,之前的路由原理文章中介绍过,Route 内部通过消费 context 方式,当路由改变的时候,消费 context 的 Route 会重新渲染,内部通过 match 匹配到当前的路由组件是否挂载,那么就是说真正去匹配,去挂载的核心组件为 Route。
而在新版本的 Route 中,对于路由更新,到路由匹配,再到渲染真正的页面组件,这些逻辑主要交给了 Routes ,而且加了一个?branch ?‘分支’ 的感念。可以把新版本的路由结构理解一颗分层级的树状结构,也就是当路由变化的时候,会在 Routes 会从路由结构树中,找到需要渲染 branch 分支。此时的 Route 组件的主要目的仅仅是形成这个路由树结构中的每一个节点,但是没有真正的去渲染页面。
新版本的路由可以说把路由从业务组件中解耦出来,路由的配置不在需要制定的业务组件内部,而是通过外层路由结构树统一处理。对于视图则是通过?OutletContext ?来逐层传递,接下来我们一起来看一下细节。
2 外层容器,更新源泉 BrowserRouter | HashRouter | Router
老版本的 BrowserRouter
import { createBrowserHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
history = createHistory(this.props)
render() {
return <Router history={this.history} children={this.props.children} />;
}
}
- 老版本的 BrowserRouter 就是通过?
createHistory ?创建?history ?对象,然后传递给 Router 组件。
新版本的 BrowserRouter
export function BrowserRouter({
basename,
children,
window
}: BrowserRouterProps) {
/* 通过 useRef 保存 history 对象 */
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window });
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
/* history 变化,通知更新。 */
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
新版本的 BrowserRouter 的功能如下:
- 通过?
createBrowserHistory ?创建?history ?对象,并通过?useRef ?保存 history 对象。 - 通过?
useLayoutEffect ?来监听?history ?变化,当 history 发生变化(浏览器人为输入,获取 a 标签跳转,api 跳转等 )。派发更新,渲染整个 router 树。这是和老版本的区别,老版本里面,监听路由变化更新组件是在 Router 中进行的。 - 还有一点注意的事,在老版本中,有一个?
history ?对象的概念,新版本中把它叫做?navigator ?。
新版本 Router
function Router({basename,children,location:locationProp,navigator}){
/* 形成 navigationContext 对象 保存 basename , navigator 对象等信息。*/
let navigationContext = React.useMemo(
() => ({ basename, navigator, static: staticProp }),
[basename, navigator, staticProp]
);
/* 把 location 里面的状态结构出来 */
const { pathname, search, hash, state, key } = locationProp
/* 形成 locationContext 对象,保存 pathname,state 等信息。 */
let location = React.useMemo(() => {
/* .... */
return { pathname, search, hash, state, key }
},[basename, pathname, search, hash, state, key])
/* 通过 context 分别传递 navigationContext 和 locationContext */
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location, navigationType }}
/>
</NavigationContext.Provider>
)
}
Router ?在新版路由中充当的角色如下:
- 通过 useMemo 来派生出负责跳转路由等功能的 navigator 对象和路由信息的 location 对象。通过 React context 来传递它们。
- 当路由变化时候,在?
BrowserRouter ?中通过 useState 改变 location ,那么当 location 变化的时候,LocationContext ?发生变化,消费 LocationContext 会更新。
3 原理深入,Routes 和 branch 概念
Routes 和 useRoutes
首先来看一下?Routes ?的实现
export function Routes({children,location }) {
return useRoutes(createRoutesFromChildren(children), location);
}
使用?<Routes /> ?的时候,本质上是通过 useRoutes 返回的 react element 对象,那么可以理解成此时的 useRoutes 作为一个视图层面意义上的?hooks ?。 Routes 本质上就是使用 useRoutes 。
useRoute
function useRoutes(routes, locationArg) {
let locationFromContext = useLocation();
/* TODO: 第一阶段:计算 pathname */
// ...代码省略
/* TODO: 第二阶段:找到匹配的路由分支 */
let matches = matchRoutes(routes, {
pathname: remainingPathname
});
console.log('----match-----',matches)
/* TODO: 第三阶段:渲染对应的路由组件 */
return _renderMatches(matches && matches.map(match => Object.assign({}, match, {
params: Object.assign({}, parentParams, match.params),
pathname: joinPaths([parentPathnameBase, match.pathname]),
pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, match.pathnameBase])
})), parentMatches);
}
路由状态传递
首先我们知道 reduceRight 是从右向左开始遍历,那么之前讲到过 match 结构是 root -> children -> child1, reduceRight 把前一项返回的内容作为后一项的 outlet,那么如上的 match 结构会这样被处理。
- 1 首先通过 provider 包裹 child1,那么 child1 真正需要渲染的内容 Child1 组件 ,将被当作 provider 的 children,最后把当前 provider 返回,child1 没有子路由,所以第一层 outlet 为 null。
- 2 接下来第一层返回的 provider,讲作为第二层的 outlet ,通过第二层的 provider 的 value 里面 outlet 属性传递下去。然后把 Layout 组件作为 children 返回。
- 3 接下来渲染的是第一层的 Provider ,所以 Layout 会被渲染,那么 Child1 并没有直接渲染,而是作为 provider 的属性传递下去。
三 v5 和 v6 区别总结
组件层面上:
- 老版本路由采用了 Router Switch Route 结构,Router -> 传递状态,负责派发更新; Switch -> 匹配唯一路由 ;Route -> 真实渲染路由组件。
- 新版本路由采用了 Router Routes Route 结构,Router 为了抽离一 context; Routes -> 形成路由渲染分支,渲染路由;Route 并非渲染真实路由,而是形成路由分支结构。
使用层面上:
- 老版本路由,对于嵌套路由,配置二级路由,需要写在具体的业务组件中。
- 新版本路由,在外层统一配置路由结构,让路由结构更清晰,通过 Outlet 来实现子代路由的渲染,一定程度上有点类似于 vue 中的?
view-router 。 - 新版本做了 API 的大调整,比如 useHistory 变成了 useNavigate,减少了一些 API ,增加了一些新的 api 。
原理层面上:
- 老版本的路由本质在于 Route 组件,当路由上下文 context 改变的时候,Route 组件重新渲染,然后通过匹配来确定业务组件是否渲染。
- 新版本的路由本质在于 Routes 组件,当 location 上下文改变的时候,Routes 重新渲染,重新形成渲染分支,然后通过 provider 方式逐层传递 Outlet,进行匹配渲染。
|