react-router浅谈

1,665 阅读6分钟

一. 前言

react-route作为react框架下的单页面应用最普及的路由,依赖histroy包提供的histroy对象作为其核心,具有使用简单、使用范围广的特点。本文主要分析了一些常用组件的使用方法以及实现实现原理,欢迎大家讨论、斧正。

二. 组件详解

1. 依赖版本

依赖 版本
react-router-dom 5.2.0
react-router 5.2.0
react 16.12.0
react-dom 16.12.0

2. 上代码

<HashRouter>
    <Route path="/about">
        <About />
    </Route>
    <Route path="/users">
        <Users />
    </Route>
    <Route path="/topics">
        <Topics />
    </Route>
    <Redirect path="/">
        <Home />
    </Route>
</HashRouter>

这是我们比较常见的写法,最外层HashRouter代表了页面的路由协议。除了HashRouter外,我们还可以选择BrowserRouter和MemoryRouter。
Router和Redirect作为二级节点,是路由协议可以匹配并且展示的组件,上面示例中的写法会导致和同时被匹配并展示,可以通过在Route上添加exact={true}属性值 或者在外层包裹Switch组件避免。 如下:

<HashRouter>
    <Switch>
        <Route path="/about">
            <About />
        </Route>
        <Route path="/users">
            <Users />
        </Route>
        <Route path="/topics">
            <Topics />
        </Route>
        
    </Switch>
</HashRouter>

3.1 HashRouter

1) 顾名思义,HashRouter工作原理就是监听hashchange来实现路由功能。
假定四个组件在hash路由下完整路径如下:
<About/>: http://localhost:3000/#/test/about
<Users/>: http://localhost:3000/#/test/users
<Topics/>: http://localhost:3000/#/test/topics
<Home/>: http://localhost:3000/#/test/

2) 属性:

basename

hash头部通用路径,如上所有的组件链接对应的hash的头部路径都是/test。我们在写Route时候,需要每个Route都的path属性都写成"/test/xxx”。如果basename属性写为"test", 则所有Route的path属性都可以写成"/xxx", histroy自动就帮我们把test拼上。

hashType

hash标识符的样式,有3种样式可以选择,功能上无任何区别

'slash' : http://localhost:3000/#/test/about     
'noslash': http://localhost:3000/#test/about     
'hashbang': http://localhost:3000/#!/test/about  

3) 优点:
浏览器链接的hash变化并不会导致浏览器重新请求服务器,在服务端还不支持h5 histroy的情况下,是个非常不错的选择。

3.2 BrowerHistroyRouter

1) BrowerHistroyRouter,通过监听popstate变化来实现路由功能,切换路由时调用window.histroy.pushState()或者window.histroy.replaceState()来实现浏览器地址栏变化。
需要服务端支持,不然强制刷新页面可能会导致请求不到资源。
假定四个组件在hash路由下完整路径如下:
<About/>: http://localhost:3000/test/about
<Users/>: http://localhost:3000/test/users
<Topics/>: http://localhost:3000/test/topics
<Home/>: http://localhost:3000/test/

2) 属性:
basename: 同HashRouter
getUserConfirmation: 同HashRouter
forceRefresh: 是否强制刷新页面,默认false
keyLength: key长度,默认6

3) 优点: 链接路径看上去会优雅点。

3.3 MemoryRouter

1) MemoryRouter 一般用在不存在浏览器的环境中,比如单元测试、react-native,底层自定义了栈来管理路由切换。

2) 属性:
initialEntries: 初始栈;
initialIndex: 初始页面在栈中位置; getUserConfirmation: 同HashRouter;
keyLength: 同BrowerHistroy;

3) 优点:
适用于无浏览器环境,即无法使用window.location/window.histroy等api的情形,可以自定义一套无需http开头的scheme路由协议,可扩展性强,使用范围广。

3.4 Switch

<Switch>组件会在child中从上往下查找path和location.pathname匹配的<Route>,找到后就会设置它的computedMatch值,用来告诉<Route/>你就是天选之子,并且终止查找并返回。

3.5 Route

1) Route的render的方法执行的时候,它还需要自己判断下自己是否被match, 以及如果match上,它该渲染children/component/render属性中的哪一个。一般情况下只写componet属性值就可以了,大家可以根据实际情况取舍。核心代码如下:

render() {
    //第一步: 判断是否匹配上
    const match = this.props.computedMatch // 父组件<Switch>会设置此值,如果父组件不是<Swtich>,则此值是undefined
            ? this.props.computedMatch 
            : this.props.path 
            ? matchPath(location.pathname, this.props) //通过matchPath()计算是否匹配
            : context.match;//兜底逻辑,不写path属性,会被match上
    //第二步: 根据第一步结果以及children/component/render的优先级先后的关系选择子节点
    return (
            <RouterContext.Provider value={props}>
              {props.match
                ? children // children优先级最高
                  ? typeof children === "function"
                    ? __DEV__
                      ? evalChildrenDev(children, props, this.props.path)
                      : children(props)
                    : children
                  : component // 其次是component
                  ? React.createElement(component, props)
                  : render // 最后才是render
                  ? render(props)
                  : null
                : typeof children === "function"
                ? __DEV__
                  ? evalChildrenDev(children, props, this.props.path)
                  : children(props)
                : null // 不匹配返回null
                }
            </RouterContext.Provider>
          );
}

2) exact、sensitive、strict 属性会影响匹配结果,具体规则如下:
exact: 为 true 时,则要求路径与 location.pathname 必须完全匹配

exact path URL 是否匹配
true /a /a/b false
true /a /a true
true /a /A true
false /a /a/b true

sensitive: 路径与 location.pathname 匹配时是否区分URL大小写

sensitive path URL 是否匹配
true /a /A false
false /a /A true

strict: 为true时,有斜线的路径只能匹配有斜线的location.pathname

sensitive path URL 是否匹配
true /a/ /a false
true /a/ /a/b true
true /a /a/b true
false /a/ /a/ true

3.6 withRouter

众所周知,react推崇的是组件化的思想,一个界面的可能由许许多多的component组成,如果你想从某个component中在不使用路由组件(Link/NavLink)的情况下进行路由跳转怎么办?答案之一就是使用withRouter。withRouter是一个高阶组件,原理就是使用RouterContext.Consumer为我们的组件提供RouterContext,我们可以从RouterContext拿到histroy,从而进行跳转。

3.7 Link、 NavLink

都是基于<a/>组件的封装,都提供了导航到某个路由的功能。NavLink相当于Link的加强版,支持点击后样式变化.

3.8 Redirect

Redirect, 类似于http的code 304的作用, 当location.pathname 和from属性值匹配时,重定向到to属性值对应的地址。

三. 路由跳转(区分window.histroy)

1) 原理: 直接或间接调用histroy.push()/histroy.replace()方法。
2) <Link/>或<NavLink/>或<Redirect/>组件天然支持。
3) 在不能使用如上组件的情况下,可以设法通过得到histroy对象。那么有哪些途径能拿到histroy了?

途径 获得方法
Route 的component组件 component组件内部直接获得: let histroy = this.props.histroy
withRouter(component) component组件内部直接获得: let histroy = this.props.histroy
withHistroy() react-router-dom提供的hook方法,let histroy = useHistroy()

四. location对象(区别window.location)

HashRouter下location包含pathname、search、hash三个属性。
BrowerRouter下location包含pathname、search、hash、key四个属性。
例如: histroy.push('/about?size=10&location=1#hahs'),在不同路由下拼装出来的完整链接尽管是不同的:

path BrowerRouter HashRouter
'/about?size=10&location=1#hahs' 'http://localhost:3000/#/about?size=10&location=1#hahs' 'http://localhost:3000/about?size=10&location=1#hahs'

但是这并不影响location。在BrowerRouter和HashRouter都能得到下面一致的输出:

router path location.pathname location.search location.hash window.location.pathname window.location.search window.location.hash
BrowerRouter '/about?size=10&location=1#hahs' '/about' '?size=10&location=1' '#hahs' '/about' '?size=10&location=1' '#hahs'
HashRouter '/about?size=10&location=1#hahs' '/about' '?size=10&location=1' '#hahs' '/' '' '#/about?size=10&location=1#hahs'

显而易见,histroy提供的location磨平了路由差异,让上层的Router组件逻辑更加清晰。
react-router-dom也提供了个useLocation这个hook方法,方便我们在子组件中获取到当前location对象。

五. 参考链接

juejin.cn/post/684490…