React Router 的基础通关指南

1,202 阅读6分钟

一:react-router的理解

单页和多页的区别

首先在了解react-router之前, 我们要知道 vuereact都是基于单页面的开发那么什么是单页面呢?

SPA: Single Page Application 单页面应用程序,整个应用中只有一个页面(index.html)

MPA : Multiple Page Application多页面应用程序,整个应用中有很多个页面(*.html)

优点:

  1. 加快页面响应速度,降低了对服务器的压力

    • 传统的多页面应用程序,每次请求服务器返回的都是一整个完整的页面
    • 单页面应用程序只有第一次会加载完整的页面,以后每次请求仅仅获取必要的数据
  2. 更好的用户体验,运行更加流畅 缺点: 不利于 SEO 搜索引擎优化

  • 因为 爬虫 只爬取 HTML 页面中的文本内容,不会执行 JS 代码

  • 可以通过 SSR(服务端渲染 Server Side Rendering)来解决 SEO 问题

    • 解释:先在服务器端把内容渲染出来,然后,返回给浏览器的就是纯 HTML 内容了
  • 页面静态化,比如,对于一个电商应用可以为每一个商品生产一个静态的HTML页面,静态 HTML 页面中是带有文字内容的,所以,有利于 SEO 的

react-router是什么

vue-routerreact-router等前端路由的原理区别不大,都是为了基于单页面开发的产物,为了可以实现无刷新的条件下切换显示不同的页面。

本质:当页面的URL发生变化时,页面的显示结果可以根据URL的变化而变化,但是页面不会全部更新

安装

使用react-router的步骤:

1.安装包npm i react-router-dom@5.3.0

2.导入并使用 import { HashRouter, Route, Link } from 'react-router-dom'

补充:每个路由不同的包的不同功能:

  • react-router: 实现了路由的核心功能
  • react-router-dom: 基于 react-router,加入了在浏览器运行环境下的一些功能
  • react-router-native:基于 react-router,加入了 react-native 运行环境下的一些功能
  • react-router-config: 用于配置静态路由的工具库

二:有哪些API

BrowserRouter、HashRouter

BrowserRouter-->history模式

HashRouter-->hash模式

使用方法--例子:

import React from 'react'
import Login from './Login'
import Home from './Home'
import Rect from './Rect'
import {  BrowserRouter as Router ,Route, Redirect } from 'react-router-dom'

const Java =(props)=>{
          return(
              <div>GetValue</div>
          )
  }
export default class App extends React.Component {
  render() {
    return (
      <Router>
        <Route  path='/login' component={Login}></Route>
        <Route  path='/home' component={Home}></Route>
        <Route  path='/rect' component={Rect}></Route>
        <Route  path='/java/:id' component={Java}></Route>
        <Redirect exact from='/' to='/login' component={Login}></Redirect>
      </Router>
    )
  }
}

Route

作用-->匹配路径,进行组件的渲染

Route的属性

  • path 属性:用于设置匹配到的路径
  • component 属性:设置匹配到路径后,渲染的组件
  • render 属性:设置匹配到路径后,渲染的内容
  • exact 属性:开启精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件
//path-component
<Route  path='/rect' component={Rect}>

//path-component-exact
<Route exact  path='/rect' component={Rect}>

//path-render-exact
<Route exact  path='/rect' render={()=>{
return <Rect />
}}>

Link 和 NavLink

使用import { Link, NavLink } from 'react-router-dom'

Link

Link组件最终会渲染成a标签,用于指定路由导航

  • to属性,将来会渲染成a标签的href属性
  • Link组件无法展示哪个link处于选中的效果

NavLink

NavLink组件,一个更特殊的Link组件,可以用用于指定当前导航高亮

<Link to="/" exact>首页</Link>
--------
<NavLink to="/" exact active={{color: "green"}}>首页</NavLink>

redirect

用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中,如下例子:

import React from 'react'
import ReactDom from 'react-dom'
import { HashRouter, Route, Link, Redirect } from 'react-router-dom'
import Search from './pages/Search.jsx'
import Comment from './pages/Comment.jsx'
export default function App () {
  return (
    <div>
      <h1>react路由基本使用</h1>
      <HashRouter>
     		<Switch>
          <Link to="/comment">评论</Link>
          <Link to="/search">搜索</Link>


          <Route path="/comment" component={Comment} />
          <Route path="/search" component={Search} />
             {/* <Route path="/" component={Comment} /> */}
          <Redirect from="/" to="/comment" />
        </Switch>
      </HashRouter>
    </div>
  )
}
ReactDom.render(<App />, document.getElementById('root'))

当接收到的路径没有匹配到时 将重定向到/comment

switch

问题: Route组件的匹配成功之后并不会停止,它可能会匹配多个组件

解决:

用Switch组件包裹多个Route组件。

Switch组件下,不管有多少个Route的路由规则匹配成功,都只会渲染第一个匹配的组件

import React from 'react'
import ReactDom from 'react-dom'
import {
  BrowserRouter as Router,
  Route,
  NavLink,
  Switch
} from 'react-router-dom'
const Home = () => <div>主页</div>
const Article = () => <div>文章列表页</div>
const ArticleDetail = () => <div>文章详情页</div>
export default function App () {
  return (
    <div>
      <h1>react路由基本使用</h1>
      <Router>
        <NavLink to="/">主页</NavLink>&nbsp;
        <NavLink to="/article">文章列表页</NavLink>&nbsp;
        <NavLink to="/article/123">文章详情页-123</NavLink>&nbsp;
        <hr />
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/article" component={Article} />
          <Route path="/article/123" component={ArticleDetail} />
        </Switch>
      </Router>
    </div>
  )
}
ReactDom.render(<App />, document.getElementById('root'))

处理404

import React from 'react'
import ReactDom from 'react-dom'
import {
  BrowserRouter as Router,
  Route,
  NavLink,
  Switch
} from 'react-router-dom'
const Home = () => <div>主页</div>
const Article = () => <div>文章列表页</div>
const ArticleDetail = () => <div>文章详情页</div>
const Page404 = () => <div>Page404</div>
export default function App () {
  return (
    <div>
      <h1>react路由基本使用</h1>
      <Router>
        <NavLink to="/">主页</NavLink>&nbsp;
        <NavLink to="/article">文章列表页</NavLink>&nbsp;
        <NavLink to="/article/123">文章详情页-123</NavLink>&nbsp;
        <hr />
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/article" component={Article} />
          <Route path="/article/123" component={ArticleDetail} />
          <Route component={Page404} />
        </Switch>
      </Router>
    </div>
  )
}
ReactDom.render(<App />, document.getElementById('root'))

可以看出 将404页面对应的路由放在switch就解决了

useHistory

使用import { useHistory } from "react-router-dom";

useHistory可以让组件内部直接访问history,无须通过props获取

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

const Login = () => {
  const history = useHistory();
  return (
    <div>
      <h1>login</h1>
      <button onClick={() => history.push("/")}>Go to home</button>
    </div>
  );
};

useParams

用于获取当前url匹配到的所有params

通过useParams() 获取返回URL参数的键、值对的对象

import { useParams } from 'react-router-dom'; export default function Foo(){
const params = useParams();
return ( 
    <div>
        <h1>{params.id}</h1> 
    </div> 
  )
}

useLocation

useLocation 会返回当前 URL的 location对象

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

const Login = () => {
  const { pathname } = useLocation();

  return (
    <div>
      <h1>Login</h1>
      <p>Current URL: {pathname}</p >
    </div>
  );
};

三:参数传递

路由.png

动态路由

<NavLink to="/home">登录</NavLink>

<Switch>
    ... 其他Route
    <Route path="/home/:id" component={Home}/>
    <Route component={Nanmap} />
</Switch>

获取参数的方式:

console.log(props.match.params.xxx)

search传递参数

<NavLink to="/home?name=jack&age=18">登录</NavLink>

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

获取参数的方式:

console.log(props.location.search)

to传对象

<NavLink to={{
    pathname: "/home", 
    query: {name: "rose", age: 30},
    state: {height: 1.68, address: "洛杉矶"},
    search: "?apikey=123"
  }}>
  登录
</NavLink>

获取参数的方式:

console.log(props.location)

state参数

//通过Link的state属性传递参数
<Link
className="nav" 
to={`/b/child2`} 
state={{ id: 999, name: "i love merlin" }} 
> 
   Child2 
</Link>

//注册路由(无需声明,正常注册即可):
<Route path="/b/child2" component={Test}/>

//接收参数:
import { useLocation } from "react-router-dom";
const { state } = useLocation(); 
//state参数 => {id: 999, name: "我是梅琳"}

//备注:刷新也可以保留住参数

四:路由模式进阶

HashRouter实现原理

改变hash值不会导致浏览器发送请求,就不会刷新页面

所以 hash值改变 会触发window对象的hashchange的事件,HashRouter通过hashchange事件监听URL的变化 从而对DOM操作模拟页面跳转

对应代码:

import React, { Component } from 'react';
import { Provider } from './context'
// 该组件下Api提供给子组件使用
class HashRouter extends Component {
  constructor() {
    super()
    this.state = {
      location: {
        pathname: window.location.hash.slice(1) || '/'
      }
    }
  }
  // url路径变化 改变location
  componentDidMount() {
    window.location.hash = window.location.hash || '/'
    window.addEventListener('hashchange', () => {
      this.setState({
        location: {
          ...this.state.location,
          pathname: window.location.hash.slice(1) || '/'
        }
      }, () => console.log(this.state.location))
    })
  }
  render() {
    let value = {
      location: this.state.location
    }
    return (
      <Provider value={value}>
        {
          this.props.children
        }
      </Provider>
    );
  }
}

export default HashRouter;

可以得出 HashRouter通过window.addEventListener('hashChange',callback)监听hash值的变化,并传递给其嵌套的组件, 然后通过contextlocation数据往后代组件传递

BrowserRouter

Router组件主要做的是通过BrowserRouter传过来的当前值,通过props传进来的pathcontext传进来的pathname进行匹配,然后决定是否执行渲染组件

import React, { Component } from 'react';
import { Consumer } from './context'
const { pathToRegexp } = require("path-to-regexp");
class Route extends Component {
  render() {
    return (
      <Consumer>
        {
          state => {
            console.log(state)
            let {path, component: Component} = this.props
            let pathname = state.location.pathname
            let reg = pathToRegexp(path, [], {end: false})
            // 判断当前path是否包含pathname
            if(pathname.match(reg)) {
              return <Component></Component>
            }
            return null
          }
        }
      </Consumer>
    );
  }
}
export default Route;