正在进入ing...

React-Router-Dom@5学习使用

发布时间:2022-01-23 浏览量: 470 文章分类: 前端相关

React-Router-Dom@5学习使用

由于v6改动确实太大,而且现有的中文文档是在是不全,迫于无奈还是重新在学习一下v5版本(叹气~)。

安装

由于现在安装会自动默认安装最新的版本(也就是v6),所以在安装的时候,我们需要指定一下安装的版本yarn add react-router-dom@5

路由的跳转实现

原生的HTML中依靠标签跳转不同的页面,在React中则依靠路由链接切换组件Link

// 原生
<a class="xxxxx" href="xxxxxx" >About</a>

// React
import {Link} from 'react-router-dom'
<Link className="xxxxx" to="">About</Link>

但是按照上面的模式,在引入Link后,还需要RouterBrowserRouter等相互配合进行转发才可以。

路由的引入

BrowserRouter

路径相对自然,以/间隔菜单

HashRouter

路径以#间隔,#号后面的内容都不会以资源发送给服务器

注册路由

在页面写好了链接以后,实际已经实现了页面地址随着点击按钮切换和变化,但是页面的内容并不会根据点击而随之切换。所以还需要将对应的组件注册到路由上。

通常常用的方式,直接将App函数如下包裹,下面也就是一个可以正常使用的样例

# index.js
import React from "react";
import ReactDOM from "react-dom";

import App from "./App";

import { BrowserRouter } from "react-router-dom";

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


# App.js
import React, { Component } from "react";
import { Link,Route } from "react-router-dom";

import Home from "./components/Home";
import About from "./components/About";

export default class App extends Component {
    render() {
        return (
            <div>
                <div className="row">
                    <div className="col-xs-offset-2 col-xs-8">
                        <div className="page-header">
                            <h2>React Router Demo</h2>
                        </div>
                    </div>
                </div>
                <div className="row">
                    <div className="col-xs-2 col-xs-offset-2">
                        <div className="list-group">
                            <Link className="list-group-item" to="/about">
                                About
                            </Link>
                            <Link className="list-group-item" to="/home">
                                Home
                            </Link>
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                            <div className="panel-body">
                                <Route path="/about" component={About} />
                                <Route path="/home" component={Home} />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

路由组件

组件分为一般组件(自己实现的组件)和路由组件。最大的区别还是 接收到的参数问题。除了写法不同外,常规的一般组件,需要我们通过手动去传递参数,才可以通过this.props接收到参数。

# App.js

<Header a={1} />

# Header.jsx

import React, { Component } from "react";

export default class Header extends Component {
    render() {
        console.log('Header收到的参数是:',this.props)
        return <h2>React Router Demo</h2>;
    }
}

>>> Header收到的参数是: {a: 1}

而路由组件则不一样,传递的参数如下

# App.js

<Route path="/about" component={About} />

# About.jsx

import React, { Component } from "react";

export default class About extends Component {
    render() {
        console.log('About:',this.props)
        return <div>我是About的内容</div>;
    }
}

>>> About: {history: {…}, location: {…}, match: {…}, staticContext: undefined}

路由组件会额外传递historylocationmatch三个参数。

NavLink使用

由于通常在页面会涉及点击选中等其他样式的问题,所以Link标签无法实现,这是就出现了NavLink标签,NavLink在选中的时候会自动默认给选中的标签添加一个active的样式类。如果需要自定义也可以添加activeClassName属性,也就是自定义选中的类名。

<NavLink className="list-group-item"  to="/about">About</NavLink>

<NavLink className="list-group-item" activeClassName="myActive"  to="/about">About</NavLink>

在使用NavLink样式和参数较多的时候,可以采用封装NavLink的方式来实现。

···javascript

MyNavLink.jsx

import React, { Component } from "react"; import { NavLink } from "react-router-dom";

export default class MyNavLink extends Component { render() { return ( ); } }

App.jsx

About Home

# Switch的使用
如果路由数量很多,而且匹配到一个就终止不需要在往下匹配(提升效率),这时就可以使用`Switch`组件。它会按照顺序匹配,而且一般是多个的时候才会用`Switch`包裹。

```javascript
import { Route,Switch } from "react-router-dom";

<Switch>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
</Switch>

总结: + 通常情况下,path和component是一一对应的关系 + Switch可以提高路由的匹配效率(单一匹配)

可能存在的样式丢失问题

由于路径的变化问题,可能会出现样式丢失的问题,建议在index.html里面都统一使用%PUBLIC_URL%/...来替代资源路径。

<link href="%PUBLIC_URL%/css/bootstrap.min.css" rel="stylesheet">

模糊匹配&精准匹配

默认是模糊匹配,如果添加exact关键词就是精准匹配。

// 模糊匹配
 <Route path="/about" component={About} />
// 精准匹配
 <Route exact path="/about" component={About} />

Redirect的使用

Redirect通常放在路由的最下方,用来兜底如果输入链接都未匹配到,则进行跳转Redirect的设置路由上。

import { Route,Switch,Redirect } from "react-router-dom";

<div className="list-group">
    <MyNavLink to="/about">About</MyNavLink>
    <MyNavLink to="/home">Home</MyNavLink>
</div>

<div className="panel-body">
    <Switch>
        <Route path="/about" component={About} />
        <Route path="/home" component={Home} />
        <Redirect to="/about" />
    </Switch>
</div>

嵌套路由

多级路由的实现,如果开启了严格匹配则无法进行二级路由匹配。 需要注意下面的代码中,/home是一级路径,而/home/news或者/home/message则是二级路由,路径需要一致并对应。

  • 注册子路由的时候要写上父路由的path值
  • 路由的匹配是按照注册路由的顺序进行的
<div>
    <h3>Home组件内容</h3>
    <div>
        <ul className="nav nav-tabs">
            <li> 
                <MyNavLink to="/home/news">News</MyNavLink>
            </li>
            <li>
                <MyNavLink to="/home/message">Message</MyNavLink>
            </li>
        </ul>
        <ul>
        {/* 注册路由 */}
        <Switch>
            <Route path="/home/news" component={News} />
            <Route path="/home/message" component={Message} />
            <Redirect to="/home/news" />
        </Switch>
        </ul>
    </div>
</div>

给路由组件传递参数

携带params参数传递

最大的特点就是直接展示到路径中。 + 路由链接(携带参数): <Link to={/home/message/detail/${msgObj.id}/${msgObj.title}}>{msgObj.title}</Link> + 注册路由(声明接收): <Route path="/home/message/detail/:id/:title" component={Detail} /> + 接收参数 : const {id,title} = this.props.match.params

# Message.jsx

import React, { Component } from "react";

import { Link,Route } from "react-router-dom";

import Detail from "./components/Detail";

export default class Message extends Component {
    state = {
        messageArr:[
            {id:'01',title:'消息1'},
            {id:'02',title:'消息2'},
            {id:'03',title:'消息3'},
        ]
    }
    render() {
        const {messageArr} = this.state
        return (
            <div>
                <ul>
                    {
                        messageArr.map(msgObj=>{
                            return (
                                <li key={msgObj.id}>
                                    {/* 向路由组件传递params参数 */}
                                    <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
                                </li>
                            )
                        })
                    }
                </ul>
                <hr />
                {/* 声明接受params参数 */}
                <Route path="/home/message/detail/:id/:title" component={Detail} />
            </div>
        )
    }
}

# Detail.jsx

import React, { Component } from "react";



// 消息详情
const DetailData = [
    {id:'01',content:'你好,中国'},
    {id:'02',content:'你好,我自己'},
    {id:'03',content:'你好,广州'},
]


export default class Detail extends Component {
    render() {
        // 接受params参数
        const {id,title} = this.props.match.params
        const findResult = DetailData.find((detailObj)=>{
            return detailObj.id === id
        })
        return (
            <ul>
                <li>ID:{id}</li>
                <li>TITLE:{title}</li>
                <li>CONTENT:{findResult.content}</li>
            </ul>
        );
    }
}

路由链接(携带参数):<Link to={/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}}>{msgObj.title}</Link> 注册路由(无需声明,正常注册即可):<Route path="/home/message/detail" component={Detail} /> 接收参数:const {search} = this.props.location 备注:获取到的searchurlencode编码字符串,需要借助qs库来实现解析

# Message.jsx

import React, { Component } from "react";

import { Link,Route } from "react-router-dom";

import Detail from "./components/Detail";

export default class Message extends Component {
    state = {
        messageArr:[
            {id:'01',title:'消息1'},
            {id:'02',title:'消息2'},
            {id:'03',title:'消息3'},
        ]
    }
    render() {
        const {messageArr} = this.state
        return (
            <div>
                <ul>
                    {
                        messageArr.map(msgObj=>{
                            return (
                                <li key={msgObj.id}>
                                    {/* 向路由组件传递search参数 */}
                                    <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
                                </li>
                            )
                        })
                    }
                </ul>
                <hr />
                {/* 
                    声明接受search参数 
                    search参数无需声明接收,直接正常注册即可
                */}
                <Route path="/home/message/detail" component={Detail} />
            </div>
        )
    }
}


# Detail.jsx

import React, { Component } from "react";

import qs from 'qs'


// 消息详情
const DetailData = [
    {id:'01',content:'你好,中国'},
    {id:'02',content:'你好,我自己'},
    {id:'03',content:'你好,广州'},
]


export default class Detail extends Component {
    render() {
        // 接收search参数
        const {search} = this.props.location
        const {id,title} = qs.parse(search.slice(1))


        const findResult = DetailData.find((detailObj)=>{
            return detailObj.id === id
        })
        return (
            <ul>
                <li>ID:{id}</li>
                <li>TITLE:{title}</li>
                <li>CONTENT:{findResult.content}</li>
            </ul>
        );
    }
}

state参数传递

state属于隐藏传递参数,不会在地址栏展示。但是存在如果分享链接等别人无法打开。类似(POST)请求的模式。 路由链接(携带参数):<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link> 注册路由(无需声明,正常注册即可):<Route path="/home/message/detail" component={Detail} /> 接收参数:const {id,title} = this.props.location.state 备注:本机刷新可以保留,会在缓存记录。

# Message.jsx

import React, { Component } from "react";

import { Link,Route } from "react-router-dom";

import Detail from "./components/Detail";

export default class Message extends Component {
    state = {
        messageArr:[
            {id:'01',title:'消息1'},
            {id:'02',title:'消息2'},
            {id:'03',title:'消息3'},
        ]
    }
    render() {
        const {messageArr} = this.state
        return (
            <div>
                <ul>
                    {
                        messageArr.map(msgObj=>{
                            return (
                                <li key={msgObj.id}>
                                    {/* 向路由组件传递state参数 */}
                                    <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
                                </li>
                            )
                        })
                    }
                </ul>
                <hr />
                {/* 
                    声明接受state参数 
                    state参数无需声明接收,直接正常注册即可
                */}
                <Route path="/home/message/detail" component={Detail} />
            </div>
        )
    }
}


# Detail.jsx

import React, { Component } from "react";

// 消息详情
const DetailData = [
    {id:'01',content:'你好,中国'},
    {id:'02',content:'你好,我自己'},
    {id:'03',content:'你好,广州'},
]


export default class Detail extends Component {
    render() {
        // 接收state参数
        console.log(this.props);
        const {id,title} = this.props.location.state || {}


        const findResult = DetailData.find((detailObj)=>{
            return detailObj.id === id
        }) || {}
        return (
            <ul>
                <li>ID:{id}</li>
                <li>TITLE:{title}</li>
                <li>CONTENT:{findResult.content}</li>
            </ul>
        );
    }
}

路由使用总结

整体来说,个人感觉params还是比较经常使用的,如果网页涉及分享等就不要用state方式,对传递数据有隐私要求等可以使用。

路由跳转的模式

系统默认使用的是push模式进行跳转的,所以在页面会留下访问痕迹(栈内存地址)。 replace模式,则只要在Link中添加replact关键字即可。 + 比如当前页面无权限访问,可以使用replace跳转到无权限页面 + 或者支付成功后,不让页面后退,避免重复支付等

<Link replace to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>

编程式路由导航

借助this.props.history对象上的API进行路由跳转、前进、后退等操作。 + this.props.history.push() + this.props.history.replace() + this.props.history.goBack() + this.props.history.goForward() + this.props.history.go()

import React, { Component } from "react";

import { Link,Route } from "react-router-dom";

import Detail from "./components/Detail";

export default class Message extends Component {
    state = {
        messageArr:[
            {id:'01',title:'消息1'},
            {id:'02',title:'消息2'},
            {id:'03',title:'消息3'},
        ]
    }

    replaceShow = (id,title)=>{
        // replace跳转+携带params参数
        this.props.history.replace(`/home/message/detail/${id}/${title}`)
    }
    pushShow = (id,title)=>{
        // push跳转++携带params参数
        this.props.history.push(`/home/message/detail/${id}/${title}`)
    }


    render() {
        const {messageArr} = this.state
        return (
            <div>
                <ul>
                    {
                        messageArr.map(msgObj=>{
                            return (
                                <li key={msgObj.id}>
                                    <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
                                    <button onClick={()=>this.pushShow(msgObj.id,msgObj.title)}>push查看</button>&nbsp;&nbsp;&nbsp;
                                    <button onClick={()=>this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
                                </li>
                            )
                        })
                    }
                </ul>
                <hr />
                {/* 
                    声明接受state参数 
                    state参数无需声明接收,直接正常注册即可
                */}
                <Route path="/home/message/detail/:id/:title" component={Detail} />
            </div>
        )
    }
}

如果是search参数类型则可以如下。

    replaceShow = (id,title)=>{
        // replace跳转+携带search参数
        this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`)
    }
    pushShow = (id,title)=>{
        // push跳转++携带search参数
        this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)
    }

如果是state传参,也可以如下方式

    replaceShow = (id,title)=>{
        // replace跳转+携带state参数
        this.props.history.replace('/home/message/detail/',{id:id,title:title})
    }
    pushShow = (id,title)=>{
        // push跳转++携带state参数
        this.props.history.push('/home/message/detail/',{id:id,title:title})
    }

history中的goBackgoForward也是直接支持在页面操作前进后退功能的。 go则支持自定义跳转页数。

    back = ()=>{
        this.props.history.goBack()
    }

    forward = ()=>{
        this.props.history.goForward()
    }

    go = ()=>{
        this.props.history.go(2)
    }

    <button onClick={this.back}>回退</button>
    <button onClick={this.forward}>前进</button>
    <button onClick={this.go}>Go</button>

自定义页面打开跳转。

import React, { Component } from "react";

export default class News extends Component {
    componentDidMount(){
        setTimeout(()=>{
            this.props.history.push('/home/message')
        },2000)
    }

    render() {
        return (
            <ul>
                <li>news01</li>
                <li>news02</li>
                <li>news03</li>
            </ul>
        );
    }
}

withRouter 使用

一般组件是无法进行跳转的。因为没有对应的路由传递的三大参数。可以使用withRouter解决一般组件具备路由组件特有的API。

import React, { Component } from "react";

import { withRouter } from "react-router-dom";

class Header extends Component {
    back = () => {
        this.props.history.goBack();
    };

    forward = () => {
        this.props.history.goForward();
    };

    render() {
        return (
            <div>
                <h2>React Router Demo</h2>
                <button onClick={this.back}>回退</button>
                <button onClick={this.forward}>前进</button>
            </div>
        );
    }
}

export default withRouter(Header)

BrowserRouterHashRouter的区别

  • 底层原理不一样 BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。 HashRouter使用的是URL的哈希值。
  • url表现形式不一样 BrowserRouter的路径中没有#,比如: localhost:3000/demo/test HashRouter的路径包含#,比如: localhost:3000/#/demo/test
  • 刷新后对路由state参数的影响 BrowserRouter没有任何影响,因为state保存在history对象中 HashRouter刷新后会导致state参数丢失
  • HashRouter可以用于解决一些路径错误相关的问题。