为什么需要路由?

站点的构成

微博是新浪的一个产品,也是一个站点,一个站点下包含很多个功能块,比如微博包括:新闻、短视频等,而功能块之间多多少少有点联系。一个功能块可能就是一个单页面应用

无论是使用vue还是react,开发的单页应用程序,可能只是该站点的一部分(某一个功能块)做成一个
一个单页应用里,可能会划分为多个页面(几乎完全不同的页面效果)(组件)

如果要在单页应用中完成多个页面(组件)的切换,需要实现如下两个功能:

  1. 根据不同的页面地址,展示不同的组件(这是最重要的)
  2. 完成无刷新的地址切换(更好的用户体验)

我们把 实现了以上两个功能的插件,称之为 路由

React Router

  1. react-router:路由核心库,包含诸多和路由功能相关的核心代码
  2. react-router-dom:利用路由核心库,结合实际页面,实现跟页面路由密切相关的功能

如果是在页面中实现路由,需要安装 react-router-dom

两种路由模式

路由:根据不同的页面地址,展示不同的组件

我们要根据哪些信息产生不同的组件呢?

url地址组成

例:https://www.react.com:443/news/1-2-1.html?a=1&b=2#abcdefg

  1. 协议名(schema):https
  2. 主机名(host):www.react.com
    1. ip 地址
    2. 预设值:localhost
    3. 域名:www.react.com
    4. 局域网中计算机名
  3. 端口号(port):443
    1. 若协议为http,则端口号默认为80,且可以省略;https的端口号默认为443,也可省略
  4. 路径(path):/news/1-2-1.html
    1. 第一个 / 代表根路径
  5. 地址参数(search/query):?a=1&b=2
    1. 附带的数据
    2. 格式:属性名=属性值&属性名=属性值…
  6. 哈希(hash/锚点):#abcdefg
    1. 附带的数据
    2. 读作sharp,是个音乐符

      Hash Router 哈希路由

      根据url地址中的哈希值来确定要显示的组件

      原因:hash的变化,不会导致页面重新刷新 ps:这种模式的兼容性最好

如何获取hash值呢?

  1. const urlHash = location.hash

Browser History Router 浏览器历史记录路由

HTML5 新增了 History API,自此,浏览器拥有了 改变url的路径(path)而不刷新页面的方式
History表示浏览器的历史记录,使用栈存储

  1. window.history
  1. history.length:栈中的数据量(当前页面。新开的页面肯定不算啊,那都不是单页面了)
  2. history.pushState:向当前历史记录中加入一条新的记录
    1. 参数1:附加的数据,自定义的数据,可以是任何类型
    2. 参数2:页面标题,目前大部分浏览器不支持!
    3. 参数3:url的路径path,新的地址
  3. history.replaceState:将当前指针指向的历史记录,替换为某个记录。参数同上
  4. history.go/back/forward

根据页面的路径(url中的path那一部分)决定渲染哪个组件

路由组件

React-Router 为我们提供了两个重要组件

Router组件

  1. 它本身不做任何展示,仅提供路由模式配置。
  2. 该组件会产生一个上下文,上下文中会提供一些实用的对象的和方法,供其他相关组件实用

  3. HashRouter:该组件使用hash模式匹配

  4. BrowserRouter:该组件使用BrowserHistory模式匹配

通常情况下,Router组件只有一个,要将该组件包裹整个页面(一般包裹住App.js的根元素即可)

Route组件

根据不同的地址,展示不同的组件

Route组件的重要属性:

  1. path:匹配的目录路径(默认情况下,不区分大小写)
    1. 默认不区分大小写,可以设置sensitive属性为true,来区分大小写
    2. 默认会匹配初始目录的路径上的所有组件,比如path=”/a/b”就会把A和B组件都匹配到,加上 exact,才会只匹配到组件B
    3. 如果想要任意目录都匹配到组件,则不写 path 属性即可
  2. component:匹配成功后要显示的组件
  3. children:
    1. 传递React元素,无论是否匹配,一定会忽略component属性?还是说,只是一定会加载children。匹配到path才会显示children元素
    2. 传递一个函数,该函数有多个参数,这些参数来自于上下文,该函数返回react元素。无论当前地址和path路径匹不匹配,都将会执行children对应的函数。
    3. 上述都是在非Switch包裹下才会按照那样的规则

TO DO:上述有待实践考察。!!!!
Route组件可以写到任意地方,只需保证是Router组件的后代元素即可

children是Route组件的这三个属性中优先级最低的,在Route组件中, component , render ,children是可以同时存在的。同时存在的条件下有这样一个优先级顺序:component > render > children…

Switch组件

写到Switch中的Route组件,只要匹配到第一个Route组件后,就会立即停止匹配!
Switch组件内部只能写Route组件。

Switch会掌控Route的渲染

由于Switch组件会循环所有子元素,然后让每个子元素依次去完成匹配,若匹配到,则渲染对应组件,然后立即停止循环。因此,不能在Switch的子元素中使用除Route组件外的组件

  1. import React, { Component } from "react";
  2. // import Test from './components/CheckBoxGroup/Test';
  3. // import Test from "./Comp1";
  4. // import Test from "./FuncDefault.js"
  5. import { BrowserRouter, Route } from "react-router-dom";
  6. function A(){
  7. return (
  8. <h1>组件A</h1>
  9. )
  10. }
  11. function B(){
  12. return (
  13. <h1>组件B</h1>
  14. )
  15. }
  16. function C(){
  17. return (
  18. <h1>组件C</h1>
  19. )
  20. }
  21. function D(){
  22. return (
  23. <div>
  24. <h1>找不到页面</h1>
  25. <Route exact path="/abc" component={E}></Route>
  26. </div>
  27. )
  28. }
  29. function E(){
  30. return (
  31. <span>E组件</span>
  32. )
  33. }
  34. export default class App extends Component {
  35. render() {
  36. return (
  37. <BrowserRouter>
  38. {/* <Switch> */}
  39. <Route path='/a' component={A} >
  40. <h1 style={ { color: "red" } }>匹配到a就出现!无论是否匹配到a都会忽略A</h1>
  41. </Route>
  42. <Route sensitive path='/a/b' component={B}/>
  43. <Route exact path='/a/c' component={C} />
  44. <Route component={D}></Route>
  45. {/* </Switch> */}
  46. </BrowserRouter>
  47. );
  48. }
  49. }

搭建后台管理模版

路由信息

Router组件会创建一个上下文,并且,向上下文中注入一些信息

该上下文对开发者是隐藏的,Route组件若匹配到了地址,则会将这些上下文中的信息作为属性传入对应的组件

三个上下文中的属性分别为:history、location、match。如今有第四个了,staticContext

history

它并不是window.history对象,是经过处理封装的。我们利用history对象实现无刷新跳转

  • push:将某个新的地址入栈,props.history.push(‘/a’); 就会跳到组件A
    • 参数一:新的path
    • 参数二:可选,附带的状态数据,在props.history.location.state中。一般不用它
  • replace:将某个新的path替换掉当前栈中的path
  • pop、go、forward、back与window.history用法一致

    为什么没有直接使用window.history对象?

  1. React-Router中有两种模式:Hash、History,如果使用了window.history则只能使用History模式
  2. 当使用window.history.pushState方法时,没有办法收到任何通知,将导致React无法知晓地址发生的变化,结果导致无法重新渲染组件。

    location

    props.location与props.history.location完全一致,是同一个对象,但是与window.location不同!

location对象中记录了当前地址的相关信息,hash,pathname,search,state。

我们通常使用第三方库 query-string 用于解析地址栏中的数据

match

该对象中保存了,路由匹配的相关信息:isExact, params, path, url

  • isExact:表示 实际上,目前的真实路径和路由组件的路径是否是真正完全匹配的。注:并不取决于exact
  • params:见下述
  • path是匹配规则,此处url是最终真实的整个链接中的path部分的值

    向某个页面传递数据的方式

  1. 使用state:在push页面时,加入state
  2. 利用search:把数据填写到地址栏中的?后,这个常用一些
    1. /new/?year=2021&month=9&day=7
  3. 利用hash:把数据填写到hash后面
  4. params:把数据填写到路径中
    1. /new/2021/9/7

实际上,在书写Route组件的path属性时,可以书写一个 string pattern 字符串正则,类似正则表达式

  1. // 不一定用/分割,用-等都行,冒号后就是表示一个变量,问号表示可传可不传,
  2. // 括号中就是对前面变量的约束,使用正则表达式。*代表后面必须跟任意字段
  3. <Route path="/news?/:year?/:month?/:day/*" componen={News} />
  4. <Route path="/news-:year(\d+)-:month(\d+)-:day(\d+)" componen={News} />
  5. //path可以是数组的形式
  6. <Route path={["/news", "/news?/:year?/:month?/:day/*"]} componen={News} />
  7. //匹配的年月日就在params中
  8. props.match.params

React-Router是利用Path-to-Regexp,将一个字符串正则转换成一个真正的正则表达式

非路由组件如何获取路由信息

由于某些组件,并没有直接放到Route中,而是嵌套在其他普通组件中,因此,它的props中没有路由信息,如果这些组件需要获取到路由信息,可以使用下面两种方式:

  1. 将路由信息在组件阶层中层层传递下去,
  2. 使用React-Router提供的高阶组件 withRouter,包装要使用的组件,该高阶组件会返回一个新组件,新组件中会注入路由信息
    1. function withRouter(Comp){
    2. return function RouterWrapper(props){
    3. // 获取上下文中的路由信息, 我们拿不到,但react拿得到
    4. return <Comp {...props} history={上下文中的history} />
    5. }
    6. }

    其他组件

    已学习:
  • Router:BrowserRouter、HashRouter
  • Route
  • Switch
  • 高阶函数:withRouter

    Link

    生成一个无刷新跳转的a元素 ```javascript // Link.js function Link(props){ // 原理性模拟 return ( // staticContext不能放在真实dom上,所以这控制台会报红色警告
    1. <a href={props.to} {...props}
    2. onClick={(e)=>{
    3. e.preventDefault();
    4. props.history.push(props.to);
    5. }}
    6. >{props.children}</a>
    ) }

export default withRouter(Link) ```

  • to属性
    • 字符串,跳转的目标地址
    • 对象:pathname,search,hash,state
  • replace属性:布尔值,表示是否替换当前地址。默认是false,即push方式跳转
  • innerRef属性:可以将内部的a元素的ref附着在传递的对象或函数参数上

    • 函数
    • ref对象

      NavLink

      是一种特殊的Link,具备所有Link组件的功能
      它具备的额外功能是:根据浏览器导航栏当前实际地址和a标签的href属性,来决定该链接的样式
  • activeClassName:匹配到时,使用的类名,需要我们自己给这个类写css

  • activeStyle:匹配到时,使用的内联样式
  • exact:是否精确匹配
  • sensitiive:匹配时是否区分大小写
  • strict:匹配时是否严格匹配最后一个斜杠

    Redirect

    重定向组件,当加载到该组件时,会自动跳转(无刷新)到另外一个地址
    一般可以配合Switch使用,让Redirect兜底

  • to属性:表示要跳转到的地址,字符串/对象

  • push:默认为false,表示跳转使用替换的方式
  • from:当要跳转到的地址匹配from地址规则时才进行重定向跳转
    • 还能把它匹配的结果放进to中,注意变量名称需要一致
  • exact、sensitive、strict

    vue与react router简单对比

    vue-router 一般是静态的配置(当然也可以动态的配置)
    react-router v4之前 也是静态的配置
    react-router v4及之后,动态的组件,非常的灵活