React基础-路由(react-router-dom)

329 阅读5分钟

前端路由原理

前端路由依赖的是BOM中的history, history是个栈结构, 通过对history的拦截来实现路由的跳转
SPA: Single Page Application -- 单页面应用
单页面应用并不意味着就一个页面, 而是单个页面多个组件构成, 跳转的时候也是组件的展示与否

react-router-dom框架

    Router分为两种
        BrowserRouter: 符合正常页面跳转的情况
        HashRouter: url中会出现#号

BrowserRouter

    // 跳转标签, 类似<a>
    // NavLink标签必须要嵌入到 BrowserRoute里, 这里还有一个NavLink 标签可以有高亮, Link 是没有的
    // NavLink 标签里有一个属性activeClassName 用于设置高亮的样式
    <BrowserRouter>
        <NavLink to="/xxxpage"><NavLink> // 也是被转为a标签
    </BrowserRouter>
    
    // 注册路由
    <BrowserRouter>
         <Route path="/page1" component={ Page1Component }/> 
         <Route path="/page2" component={ Page2Component }/>
    </BrowserRouter>

注意:像上面这种, 使用NavLink/Link标签也好Route标签也好, 都是需要嵌套在BrowserRouter这个标签里面的, 但是上面这种情况会出现一个问题, 使用多个BrowserRouter包裹的时候会出现多个路由对象, 他们互相没什么关系, 所以通常做法是把整个<App />组件进行包裹, 像下面这样

ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>, 
    document.getElementById('root')
)

HashRouter

兼容更好, 但是刷新后会怼state参数有影响, 因为 BrowserRouter的state存放在history中
当然, 这里也能够使用HashRouter, 如果使用这个访问服务器的时候的url
    http://localhost:3000/# 
跳转页面就会出现这种情况
    http://localhost:3000/#/about
还需要注意一点就是 #后面的数据是不会被提交给服务器的, 被认为是本地的资源

ReactDOM.render(
    <HashRouter>
        <App />
    </HashRouter>, 
    document.getElementById('root')
)

组件的分类

路由组件: 一般放到pages目录
    <Route path="/page1" component={ Page1Component }/>

一般组件: 一般放到components目录
    <Page />

路由组件进行跳转, 准确的说是挂载组件的时候, 会有一个默认传值props, 三个重要的属性history location match

image.png

自定义导航组件

如果有多个这种导航组件就会出现下面的情况, 样式都一样, 只是path不一样而已
<Route activeClassName="" className="" path="/page1" component={ Page1Component1 }/> 
<Route activeClassName="" className="" path="/page2" component={ Page1Component2 }/> 
<Route activeClassName="" className="" path="/page3" component={ Page1Component3 }/> 

这个时候就需要自定义一个组件
1. 新建组件MyNaviLink 导入 NavLink
2. 返回 NavLink 组件就好

return (
    <NaviLink activeClassName="" className="" {...this.props} />
)

3. 外面调用的时候
    <MyNavLink to="/about">About</MyNavLink>

这里需要注意一个问题 标签中的 About字符, 在自定义里并没有设置, 是因为两个标签的字符, 会以一个叫
children:'About'的字段存在于props里, 通过children="About" 设置能达到同样的效果

Swtich标签: 当匹配到一个path之后, 就不会再继续向下匹配, 相当于退出循环

如果有很多的路由, 相同的path 对应不同的组件的话, 默认会从上到下一直匹配, 有一个算一个
<Switch>
    <Route activeClassName="" className="" path="/page1" component={ Page1Component1}/> 
    <Route activeClassName="" className="" path="/page2" component={ Page1Component2 }/> 
    <Route activeClassName="" className="" path="/page3" component={ Page1Component3 }/> 
    <Route activeClassName="" className="" path="/page3" component={ Page1Component4 }/> 
</Switch>

如果不包裹Switch的话, 当匹配到page3的时候, 会出现两个组件

多级路径导致的样式丢失问题

如果使用BrowserRouter的时候, 在path上添加统一标识, 路径应该是 /page1 想添加统一标识/xxx/page1, 跳转的时候不会有任何问题,
但是一旦请求服务器的时候, 有可能会导致资源加载错误, 会把xxx带入到服务器中
http://localhost:3000/css/index.css
http://localhost:3000/xxx/css/index.css

解决办法

1. 首页在引入css样式表的时候, 使用绝对路径 <link rel="stylesheet" href="/css/index.css">
// 下面这个PUBLIC_URL只有react认
2. 首页在引入css样式表的时候, 使用绝对路径 <link rel="stylesheet" href="%PUBLIC_URL%/css/index.css">
3. 使用HashRouter, 这货会添加#号, #号之后的都不会带入服务器

导航的模糊匹配和精准匹配

比如注册的路由/home 
    /home, 刚好匹配上, 可以展示组件
    /home/a, 会先把 home 和  a 分别取出来, 然后 跟home 匹配, 第一个是home 就可以跳转到home
    /a/home 第一个是a 跟home匹配不上, 就不会再往后看了
    
如果想要精准匹配, 只需要在注册的时候设置一个属性 exact= {true}即可(最好不要开启)
<Route exact={true} path="/page1" component={ Page1Component1 }/>

Redirect重定向组件

路由的匹配是按照注册的顺序开始匹配, 也就是说谁先注册先匹配谁, 匹配到谁就返回页面, 如果谁都没匹配到, 
就会直接跳转到Redirect 指定的页面

其实输入主页地址 http://lcoalhost:3000 上来也是会进行匹配 因为3000 后面还会有一个 / 
但是因为是首页, / 后面没有字符了, 就会被认为是空的,  所以需要一个兜底的操作
下面这个就是兜底的, 如果所有的路由都匹配不上, 就会跳转Redirect 指定的页面
<Redirect to="/about" />

嵌套/多级路由

about 
home 
    先注册 /about 和 /home 最后 Redirect /about
    
 现在home有两个二级页面 news message
     跳转的path分别是
         /home/news
         /home/message
     子页面的路由注册为
         /home/news
         /home/message
如果是在子页面跳转到 /home/news 页面的话规则是
    匹配到先注册的/home的时候,  这时, home 页面不会消失 
    继续向下匹配 /home/news 第二个跟跳转的news匹配, 然后再展示出news的界面

Tips1:如果是多级路由, 开启精确匹配的话, 多级路由就无效了

Tips2:多级路由就要把父组件也给加上比如上面的news下面有一个a页面, /home/news/a

路由组件传参数

params参数

    是通过路由传递的分跳转端和注册端
        注册端
            <Route path="/home/message/detail/:id/:title" component={Detail}>
            
        跳转端, 多个参数用斜杠分开/$()
            <Link to={`/home/message/detail/$(obj.id)/$(obj.title)`}>{obj.title}</Link>
        页面获取
            是在 match 的params里 
            match 就是上面路由跳转的时候, 默认的三大参数之一, 还有history和location
            const {id, title} = this.props.match.params

search参数

    是通过路由传递的分跳转端和注册端
        注册端(无需任何声明)
            <Route path="/home/message/detail" component={Detail}>
            
        跳转端, 多个参数用斜杠分开/$()
            <Link to={`/home/message/detail/?id=$(obj.id)&title=/$(obj.title)`}>{obj.title}</Link>
            
        页面获取search参数
            导入querystring库
            import qs from 'querystring'
            
            const {search} = this.props.location.search // ?id=1&title="title01"
            
            // 把对象转为urlencode
            let obj = {name:'tom', age:18} // name=tome&age=18
            qs.stringify(obj)
            
            // 解析search 参数
            const {id, title} = qs.parse(search.slice(1))
            

state参数

        注册端(无需任何声明)
            <Route path="/home/message/detail" component={Detail}>
            
        跳转端, 多个参数用斜杠分开/$()
            <Link to={{pathName:"/home/message/detail", state:{id:obj.id, title:obj.title}}}>{obj.title}</Link>
            
        页面获取参数
            const {id, title} = this.props.location.state || {}
            
            

pushreplace

    push: 路由默认的方式, 就是入栈的操作,  返回的时候是push多少次, 返回到首页就得返回同样次数
    replace: 是替换的方式, 这种一旦使用就会导致栈顶只有一个, 开启的话, 只需要在跳转的时候设置就好
    一旦开启, 就相当于history里没有记录
    <Link replace={true} to="/home">首页</Link>
    

编程式路由 -- 不单单只是通过手动触发Link/NvaLink进行跳转

this.props.history.replace(`/home/message/detail/$(id)/$(title)`)
this.props.history.push()

上面那种传参方式可以传递params和search
this.props.history.replace(/home/message/detail, {id:1, title:'title1'})

编码实现前进/后退
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go(n) // 传递正数, 前进多少个 传递负数 后退多少个

如果一般组件也想使用路由的功能

import {withRouter} from 'react-router-dom' // 注意  这里导入的不是组件而是一个函数

class Header extends Component { }

export default withRouter(Header)