学习React Router

概念

“路由”是一个随处可见的东西,比如一座城里会有若干条道路通往若干个目的地,一个司机可以选择从哪条路到哪个地方。在单页面应用中会有若干个按钮或链接指向若干个组件或页面,有个叫路由器的东西就是用来设置路径和组件的对应关系的。

路由这个概念包含了三个部分:路径、目的地、路由器

React Router

React项目的路由库是 React Router,它分为三个部分:

  • react-router:核心组件
  • react-router-dom:浏览器路由库
  • react-router-native:native端路由库

核心库会随着两个端的库安装,所以在浏览器端只需要安装react-router-dom

组件

根据路由的概念,React Router也提供了三大类组件:

  • Link:让一个元素指向某个路径,比如:<Link to="/home"首页</Link

  • Route:指定路径和组件的对应关系,比如:<Route path="/home" component={Home} /

  • Router:当链接被点击时,在若干个Route中寻找路径匹配的那一个,并渲染指定的组件

每类组件都针对不同的客户端提供了不同的组件,比如Router组件在浏览器端(其他端先不管)就提供了两种组件:<BrowserRouter><HashRouter>

BrowserRouter 和 HashRouter

这两种组件分别是基于URL的pathname段和hash段的,很大的一个差别就是使用BrowserRouter时需要对服务器做一些设置,因为pathname会被发送到服务端。

一个简单应用

上图中黑框代表整个应用<App />,由<Header /><Menu /><Content />组成。<Menu />中设置了三个菜单项,点击后分别将对应的组件渲染到<Content中 />,而<Header />则可以用来显示面包屑等组件。当前选中第三条菜单,渲染了<App3 /> 组件。这个组件内部又有两个链接,分别指向不同的组件。

在这个应用中,路由发生了嵌套:

  • App

    • /app1
    • /app2
    • /app3
      • /app3-1
      • /app3-2

路由配置

现在可以简单配置一下路由了,可以看到,不必把所有路由都放到一处。以下代码的,而各个组件的具体内容则简单放个标题也可以。

需要注意:

  • 必须用Router组件包裹整个应用,可以用HashRouter或者BrowserRouter
  • 可以在子组件内继续添加路由,但要记得把父组件所在的路由传递进去
//App.js
import React from "react";
import { HashRouter as Router, Route, Link, NavLink } from "react-router-dom";
import App1 from './App1'
import App2 from './App2'
import App3 from './App3'
import Header from './Header'
import Menu from './Menu'
import Content from './Content'

export default class MyApp extends React.Component{
render(){
   return(
       <Router basename="/myapp"
         <div
           <Header /
           /* Menu组件下传了三个Link */
           /* 像这样传进来的组件,在Menu内部通过props.children就可以访问 */ 
           <Menu
             <Link to="/app1"App1</Link        
             <Link to="/app2"App3</Link
             <Link to="/app3"App3</Link
           </Menu
         </div

         <Content
           <Route path="/app1" component={App1} /    
           <Route path="/app2" component={App2} /
           <Route path="/app3" render={()=(<App3 basePath="/app3" /)} /
         </Content
       </Router
     )
  }
}

/*
 * App3内部又有路由配置
 * 需要注意的是,主路由在App组件里,所以得把进入App3组件时已有的路由(取名basePath)传进来
*/
import { Route, Link } from "react-router-dom";
import Nav from './Nav'
import App3_1 from './App3-1'
import App3_2 from './App3-2'

export default const App3 = () = (
  <div
     <Nav
         <Link to={this.props.basePath + "/app3-1"}App3-1</Link
         <Link to={this.props.basePath + "/app3-2"}App3-2</Link
     </Nav
     <Route path={this.props.basePath + "/app3-1"} component={App3_1} /
     <Route path={this.props.basePath + "/app3-2"} component={App3_2} /
  </div
)

有了这个应用骨架,剩下的问题就好办了,接下来还需要考虑这几个问题:

1. 路由匹配

当一个<Link被点击时,其to属性的值就被拿去和Route的path属性匹配。

路由其实就是目录层级结构的表示,Route就相当于对文件系统的描述,一个Route就表示了哪个路径下有哪个组件。

路由匹配就是拿Link的to属性值去看看它能够被哪个目录所包含,比如:有三个Route,分别是//abc/abc/123,显然从前到后是包含关系。此时如果有一个路径为/abc/123/xyz的Link被点击,那么这三个Route都会匹配到。

那么问题来了,由于匹配到的Route都会被渲染,上面的结果就是点一个链接渲染了三个组件,而我们期望的是只渲染/abc/123/xyz,我只知道两个解决方法:

 /*-------------------- 方法1 -------------------*/
 /* exact:让上级Route只匹配路径完全一样的Link */
 <Route exact path="/" component={App1} /	//只匹配“/”,不匹配“/...”
 <Route exact path="/abc" component={App2} /	//只匹配“/abc”,不匹配“/abc/...”
 <Route path="/abc/123/xyz" component={App3} /
 
 /*-------------------- 方法2 -------------------*/
 /* switch:只渲染第一个匹配到的,此时要注意把上级目录的Route往下面放 */
 <Switch
   /* 虽然三个都匹配了,但Switch使得只有第一个被渲染 */
     <Route path="/abc/123/xyz" component={App3} /	
     <Route path="/abc" component={App2} /	
     <Route path="/" component={App1} /		
   </Switch

NavLink是特殊的Link,匹配到后,可以给它添加activeStyle或者activeClassName。

需要注意的是,匹配是Link和Route双方的事情,NavLink能够“感知”匹配从而改变自身样式,所以也要解决多个NavLink同时感知到匹配的问题,所以NavLink也需要使用exact,不然的话点击下级目录链接会导致所有上级目录Link变样式:

 <NavLink exact to="/" activeStyle={{color: 'red'}}App1</NavLink
 <NavLink exact to="/abc" activeStyle={{color: 'red'}}App1</NavLink
 <NavLink exact to="/abc/123/xyz" activeStyle={{color: 'red'}}App1</NavLink

3. Route的三种渲染方式

  • component:给什么渲染什么

  • render:可以在渲染之前做点别的

   <Route path="/" render={()={
   	console.log("额外的逻辑")
      return <AppX /
    }/
  • children:可以根据是否匹配渲染不同组件,是否匹配可由自动传入的参数props.match获知
   <Route path="/" children={(props)={
   	console.log("额外的逻辑")
    	return props.match ? <AppX : <AppY
    }/

无论是否匹配到,children函数都会执行。不要在Switch中使用children!

4. 更多信息

一个路由被匹配到,其对应组件就会被传入三个props:match、location、history

URL参数

Link中可以给路由传参:

 <Route path="/app/:id" component={App1} /
 <Link to="/app/:123456"To App1</Link

这个参数可以通过match.params.id拿到。

posted @ 2020-05-16 14:21  Paykan  阅读(143)  评论(0编辑  收藏  举报