React 小知识 - 路由

230 阅读6分钟

安装

// 安装 React 路由组件库
yarn add react-router-dom

一级路由

src 目录,UI库用的 bootstrap

image.png


// index.js
import React from "react"
import ReactDOM from "react-dom"
import {BrowserRouter} from "react-router-dom"

import App from "./App"

const element = <BrowserRouter><App/></BrowserRouter>

ReactDOM.render(element,document.getElementById("root"));
// App.js
import React, { Component } from 'react'

import Header from "./components/Header"
import MyRouter from "./components/MyRouter"
import MyRender from "./components/MyRender"

export default class App extends Component {
    render() {
        return (
            <div className="container">
                <Header/>
                <MyRouter/>
                <MyRender/>
            </div>
        )
    }
}
// Header
import React, { Component } from 'react'

export default class Header extends Component {
    render() {
        return (
            <div className="page-header">
                <h1>React 小知识 - 路由组件 <small>react-router-dom</small></h1>
            </div>
        )
    }
}
// MyRouter
import React, { Component } from 'react'
import {NavLink} from "react-router-dom"

export default class MyRouter extends Component {
    render() {
        return (
            <div>
                <NavLink className="btn btn-primary" to="/home">Home</NavLink>
                &nbsp;
                <NavLink className="btn btn-primary" to="/about">About</NavLink>
            </div>
        )
    }
}
// MyRender
import React, { Component } from 'react'
import {Route,Switch,Redirect} from "react-router-dom"

import Home from "../../pages/Home"
import About from "../../pages/About"

export default class MyRender extends Component {
    render() {
        return (
            <Switch>
                <Route path="/home" component={Home}></Route>
                <Route path="/about" component={About}></Route>
                <Redirect to="/about" />
            </Switch>
        )
    }
}

npm start,大概样子应该如下

image.png

敲黑板:

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

在 MyRouter 组件中的 Route 是用来注册路由的组件,使用了 Switch 则是匹配单个组件,下图是没有使用

<div>
    <Route path="/home" component={Home}></Route>
    <Route path="/about" component={About}></Route>
    <Route path="/about" component={Home}></Route>
    <Redirect to="/about" />
</div>

image.png

路径相同,但是匹配了两个组件,这个时候可以用 Switch 只匹配第一个,而不继续匹配下去

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

image.png

Redirect 则是路由重定向,避免首次加载,内容为空。可以将其的修改为下图,再查看页面变化

<Redirect to="/home" />

这里注意一下,NavLink 和 Link 的区别,简书-潇湘轮回
就个人总结而言,主要是 NavLink 支持激活后的样式,添加 activeClassName = "样式名"
由于这里的 active 类名和 bootstrap 重名了,可以手动替换其他自定义类名

<NavLink activeClassName="active" className="btn btn-primary" to="/home">Home</NavLink>

一个小技巧,可以封装一个自定义 NavLink 组件,{...this.props} 将会结构 props

// MyNavLink
import React, { Component } from 'react'
import {NavLink} from "react-router-dom"

export default class MyNavLink extends Component {
    render() {
        console.log(this.props);
        return (
            <NavLink activeClassName="active" className="btn btn-primary" {...this.props} />
        )
    }
}

改造一下 MyRouter 组件

// MyRouter
import React, { Component } from 'react'

import MyNavLink from "../MyNavLink"

export default class MyRouter extends Component {
    render() {
        return (
            <div>
                <MyNavLink to="/home" children="Home" />
                &nbsp;
                <MyNavLink to="/about" children="About" />
            </div>
        )
    }
}

避免重复添加 activeClassName 属性,其中 children 的值,看如下

<MyNavLink to="/home" children="Home" />
// 上下其实是相同的
<MyNavLink to="/home">Home</MyNavLink>
// 注意看 MyNavLink 中 console.log 的值,就会理解 children 的属性用法

嵌套路由

src 目录

image.png

// Message
import React, { Component } from 'react'

export default class Message extends Component {
    render() {
        return (
            <div>
                This page is Message
            </div>
        )
    }
}
// News
import React, { Component } from 'react'

export default class News extends Component {
    render() {
        return (
            <div>
                This page is News
            </div>
        )
    }
}

接下来,改造一下 Home 组件内容

// Home
import React, { Component } from 'react'
import {Switch,Route,Redirect} from "react-router-dom"

import MyNavLink from "../../components/MyNavLink"

import Message from "./Message"
import News from "./News"

export default class Home extends Component {
    render() {
        return (
            <div>
                <h2>This page is Home</h2>
                <div>
                    <MyNavLink to="/home/message" children="message" />
                    &nbsp;
                    <MyNavLink to="/home/news" children="news" />
                </div>
                <div>
                    <Switch>
                        <Route path="/home/message" component={Message} />
                        <Route path="/home/news" component={News} />
                        <Redirect to="/home/message" />
                    </Switch>
                </div>
            </div>
        )
    }
}

效果图 如下

image.png

多级路由 以此类推

路由传参

src 目录下 新增 Detail 组件,一级改写 Message 组件

image.png

// Detail
import React, { Component } from 'react'

export default class Detail extends Component {
    render() {
        console.log(this.props);
        return (
            <div>
                <h4>This page is detail</h4>
                <div>
                    <p>id:xx</p>
                    <p>title:xxxx</p>
                    <p>content:xxxxxx</p>
                </div>
            </div>
        )
    }
}
// Message
import React, { Component } from 'react'
import {Link,Route} from "react-router-dom"

import Detail from "../../../components/Detail"

export default class Message extends Component {
    render() {
        return (
            <div>
                <h3>This page is Message</h3>
                <div>
                    <Link className="btn btn-info" to="/home/message/detail">detail-01</Link>
                    &nbsp;
                    <Link className="btn btn-info" to="/home/message/detail">detail-02</Link>
                    &nbsp;
                    <Link className="btn btn-info" to="/home/message/detail">detail-03</Link>
                </div>
                <Route path="/home/message/detail" component={Detail} />
            </div>
        )
    }
}

效果图如下

image.png

params 传参

修改 Message 组件中的部分内容

// Message
<div>
    <Link className="btn btn-info" to="/home/message/detail/01">detail-01</Link>
    &nbsp;
    <Link className="btn btn-info" to="/home/message/detail/02">detail-02</Link>
    &nbsp;
    <Link className="btn btn-info" to="/home/message/detail/03">detail-03</Link>
</div>
<Route path="/home/message/detail/:id" component={Detail} />

点击 detail-01 按钮,注意红框中的内容

image.png

点击红框中的 match,会发现 这里的 params 的id 01 就是传过来的参数,接下去再传一个 title

image.png

修改 Message 组件中的部分内容

// Message
<div>
    <Link className="btn btn-info" to="/home/message/detail/01/Hello React">detail-01</Link>
    &nbsp;
    <Link className="btn btn-info" to="/home/message/detail/02/Hello Vue">detail-02</Link>
    &nbsp;
    <Link className="btn btn-info" to="/home/message/detail/03/Hello Angular">detail-03</Link>
</div>
<Route path="/home/message/detail/:id/:title" component={Detail} />

点击红框中的 match,会发现 这里的 params 的id 01 就是传过来的参数,接下去再传一个 title

image.png

这个就显而易见了,接下去让其内容在 Detail 组件中显示
首先更改一下 Detail 组件

// Detail
import React, { Component } from 'react'

const data = [
    {
        id:"01",
        content:"你好 React"
    },
    {
        id:"02",
        content:"你好 Vue"
    },
    {
        id:"03",
        content:"你好 Angular"
    }
]

export default class Detail extends Component {
    render() {
        console.log(this.props);
        const {id,title} = this.props.match.params;
        const resultContent = data.find((item,index,arr) => {
            return item.id === id;
        });  // 数组的 find 函数 查找符合条件的对象并返回
        return (
            <div>
                <h4>This page is detail</h4>
                <div>
                    <p>id:{id}</p>
                    <p>title:{title}</p>
                    <p>content:{resultContent.content}</p>
                </div>
            </div>
        )
    }
}

效果图如下

image.png

query 传参(search)

修改 Message 组件中的内容,记得 Detail 组件中的内容也需要修改一下,避免报错

// Message
import React, { Component } from 'react'
import {Link,Route} from "react-router-dom"

import Detail from "../../../components/Detail"

export default class Message extends Component {
    render() {
        return (
            <div>
                <h3>This page is Message</h3>
                <div>
                    <Link className="btn btn-info" to="/home/message/detail/?id=01&title=Hello React">detail-01</Link>
                    &nbsp;
                    <Link className="btn btn-info" to="/home/message/detail/?id=02&title=Hello Vue">detail-02</Link>
                    &nbsp;
                    <Link className="btn btn-info" to="/home/message/detail/?id=03&title=Hello Angular">detail-03</Link>
                </div>
                <Route path="/home/message/detail" component={Detail} />
            </div>
        )
    }
}

点击 detail-01 按钮

image.png

敲黑板: 注意这个 search 值,不能直接使用,引入一下 querystring 库,React 已经帮忙准备好了,修改 Detail 组件

// Detail
import React, { Component } from 'react'
import qs from "querystring"

const data = [
    {
        id:"01",
        content:"你好 React"
    },
    {
        id:"02",
        content:"你好 Vue"
    },
    {
        id:"03",
        content:"你好 Angular"
    }
]

export default class Detail extends Component {
    render() {
        console.log(this.props);
        const {id,title} = qs.parse(this.props.location.search.slice(1));// 问号不需要,截取问号后面的内容
        const resultContent = data.find((item,index,arr) => {
            return item.id === id;
        });  // 数组的 find 函数 查找符合条件的对象并返回
        return (
            <div>
                <h4>This page is detail</h4>
                <div>
                    <p>id:{id}</p>
                    <p>title:{title}</p>
                    <p>content:{resultContent.content}</p>
                </div>
            </div>
        )
    }
}

效果图 如下

image.png

state 传参

修改 Message 组件中的内容,记得 Detail 组件中的内容也需要修改一下,避免报错

// Message
import React, { Component } from 'react'
import {Link,Route} from "react-router-dom"

import Detail from "../../../components/Detail"

export default class Message extends Component {
    render() {
        return (
            <div>
                <h3>This page is Message</h3>
                <div>
                    <Link className="btn btn-info" to={{pathname:"/home/message/detail",state:{id:"01",title:"Hello React"}}}>detail-01</Link>
                    &nbsp;
                    <Link className="btn btn-info" to={{pathname:"/home/message/detail",state:{id:"02",title:"Hello Vue"}}}>detail-02</Link>
                    &nbsp;
                    <Link className="btn btn-info" to={{pathname:"/home/message/detail",state:{id:"03",title:"Hello Angular"}}}>detail-03</Link>
                </div>
                <Route path="/home/message/detail" component={Detail} />
            </div>
        )
    }
}

注意控制台的 state

image.png

Detail 组件内容

// Detail
import React, { Component } from 'react'

const data = [
    {
        id:"01",
        content:"你好 React"
    },
    {
        id:"02",
        content:"你好 Vue"
    },
    {
        id:"03",
        content:"你好 Angular"
    }
]

export default class Detail extends Component {
    render() {
        console.log(this.props);
        const {id,title} = this.props.location.state;
        const resultContent = data.find((item,index,arr) => {
            return item.id === id;
        });  // 数组的 find 函数 查找符合条件的对象并返回
        return (
            <div>
                <h4>This page is detail</h4>
                <div>
                    <p>id:{id}</p>
                    <p>title:{title}</p>
                    <p>content:{resultContent.content}</p>
                </div>
            </div>
        )
    }
}

效果图 如下

image.png

至此三种路由传参都解释完
这里会发现,路由传参中,Home和message按钮的颜色变成了 深蓝色,并不是红色,这里涉及到样式丢失
修改 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <!-- <link rel="stylesheet" href="./index.css"> -->
    <link rel="stylesheet" href="%PUBLIC_URL%/index.css"> <!-- 这里与注释的内容对比 -->
</head>
<body>
    <div id="root"></div>
</body>
</html>

编程式路由跳转

注意下图红框内容

image.png

修改 Detail 组件内容

// Detail
import React, { Component } from 'react'

const data = [
    {
        id:"01",
        content:"你好 React"
    },
    {
        id:"02",
        content:"你好 Vue"
    },
    {
        id:"03",
        content:"你好 Angular"
    }
]

export default class Detail extends Component {
    render() {
        console.log(this.props);
        const {id,title} = this.props.location.state;
        const resultContent = data.find((item,index,arr) => {
            return item.id === id;
        });  // 数组的 find 函数 查找符合条件的对象并返回
        return (
            <div>
                <h4>This page is detail</h4>
                <div>
                    <p>id:{id}</p>
                    <p>title:{title}</p>
                    <p>content:{resultContent.content}</p>
                    <div>
                        <button className="btn btn-success">前进</button>
                        &nbsp;
                        <button className="btn btn-success">后退</button>
                        &nbsp;
                        <button className="btn btn-success">跳转</button>
                    </div>
                </div>
            </div>
        )
    }
}

编写前进,后退,跳转事件 并绑定事件

// 前进
handleGoForward = () => {
    this.props.history.goForward();
}

// 后退
handleGoBack = () => {
    this.props.history.goBack();
}

// 跳转
handleGo = () => {
    this.props.history.go(-2);
}

// Message 
<p>id:{id}</p>
<p>title:{title}</p>
<p>content:{resultContent.content}</p>
<div>
    <button className="btn btn-success" onClick={this.handleGoForward}>前进</button>
    &nbsp;
    <button className="btn btn-success" onClick={this.handleGoBack}>后退</button>
    &nbsp;
    <button className="btn btn-success" onClick={this.handleGo}>跳转</button>
</div>

普通组件 转换为 路由组件

首先修改一下 Header 组件

// Header
import React, { Component } from 'react'

class Header extends Component {
    // 前进
    handleGoForward = () => {
        this.props.history.goForward();
    }

    // 后退
    handleGoBack = () => {
        this.props.history.goBack();
    }

    // 跳转
    handleGo = () => {
        this.props.history.go(-2);
    }

    handleNav = () => {
        this.props.history.push("/home/message/detail",{id:"02",title:"我是迪迦,我要拯救世界"});
    }

    render() {
        return (
            <div className="page-header">
                <h1>React 小知识 - 路由组件 <small>react-router-dom</small></h1>
                <div>
                    <button className="btn btn-success" onClick={this.handleGoForward}>前进</button>
                    &nbsp;
                    <button className="btn btn-success" onClick={this.handleGoBack}>后退</button>
                    &nbsp;
                    <button className="btn btn-success" onClick={this.handleGo}>跳转</button>
                    &nbsp;
                    <button className="btn btn-success" onClick={this.handleNav}>编程导航</button>
                </div>
            </div>
        )
    }
}

export default Header

如下图

image.png

会发现点击按钮直接报错,那是因为这个 Header 组件的 props 并没有提供 路由的 api,所以无法正常跳转
直接请出 withRouter 修改 Header 组件部分内容

// Header 
import React, { Component } from 'react'
import {withRouter} from "react-router-dom"

class Header extends Component {
    // 前进
    handleGoForward = () => {
        this.props.history.goForward();
    } ...
    
export default withRouter(Header)

顺便提一嘴,路由跳转有两种,默认 push 会记录导航历史,replace 则不会
以上皆为看网上教学视频,个人总结,有问题别问我,我回答不上来