React-router

路由的发展阶段

后端路由阶段
  • 早期的网站开发整个 HTML 页面是由服务器来渲染的,服务器直接生产渲染好对应的 HTML 页面,返回给客户端进行展示
  • 但是,一个网站,有很多页面,服务器是如何处理的呢?
  • 一个页面有自己对应的网址,也就是 URL
  • URL 会发送到服务器,服务器会通过正则对该 URL 进行匹配,并且最后交给一个 Controller 进行处理
  • Controller 进行各种处理,最终生成 HTML 或者数据,返回给前端
  • 这就完成了一个 IO 操作
  • 上面的这种操作,就是后端路由
  • 当我们页面中需要请求不同的路径内容时,交给服务器来进行处理,服务器渲染好整个页面,并且将页面返回给客户端
  • 这种情况下渲染好的页面,不需要单独加载任何的 js 和 css,可以直接交给浏览器展示,这样也有利于 SEO 的优化
  • 后端路由的缺点
  • 一种情况是整个页面的模块由后端人员来编写和维护的
  • 另一种情况是前端开发人员如果要开发页面,需要通过 PHP 和 Java 等语言来编写页面代码
  • 而且通常情况下 HTML 代码和数据以及对应的逻辑会混在一起,编写和维护都是非常糟糕的事情
前后端分离阶段
  • 前端渲染:
  • 每次请求设计到的静态资源都会从静态资源服务器获取
  • 这些资源包括 HTML+CSS +JS, 然后在前端对这些请求回来的资源进行渲染
  • 需要注意的是,客户端的每一次请求,都会从静态资源服务器请求文件
  • 同时可以看到,和之前的后端路由不同,这时后端只是负责提供 API 了
  • 前后端分离阶段:
  • 随着 Ajax的出现,有了前后端分离的开发模式
  • 后端只提供 API 来返回数据,前端通过 Ajax 获取数据,并且可以通过 JavaScript 将数据渲染到页面中
  • 这样做最大的优点就是前后端责任的清晰,祸端专注于数据上,前端专注于交互和可视化上
  • 并且当移动端出现后,后端不需要进行任何处理,依然使用之前的一套API 即可
  • 目前很多网站依然采用这种模式开发(jQuery 开发模式)
单页面富应用(SPA)阶段
  • 单页面富应用的理解:
  • 单页面富应用的英文是 single-page application, 简称 SPA
  • 整个Web应用实际上只有一个页面,当 URL 发生改变时,并不会从服务器请求新的静态资源
  • 而是通过 JavaScript 监听 URL 的改变,并且根据 URL 的不同去渲染新的页面
  • 如何可以应用 URL 和渲染的页面呢? 使用前端路由
  • 前端路由维护着 URL 和渲染页面的映射关系
  • 路由可以根据不同的 URL,最终让我们的框架(比如 Vue、React、Angular)去渲染不同的组件
  • 最终我们在页面上看到的实际就是渲染的一个个组件页面

前端路由的原理

  • 前端路由是如何做到 URL 和内容进行映射的呢? 通过监听 URL的改变
  • URL 发生变化,同时不引起页面和的刷新有两个办法:
  • 通过 URL 的 hash 改变 URL
  • 通过 HTML5 中的 history 模式修改 URL;
  • 当监听到 URL 发生变化时,我们可以通过自己判断当前的 URL,决定到底渲染什么样的内容
URL 的 hash
  • URL 的 hash 也就是锚点(#),本质是改变 window.location 的 href 属性;
  • 我们可以通过直接赋值 location.hash 来改变 href.但是页面不发生刷新;
  • hash 的优势是兼容性好,在老版的 IE 中都可以运行
  • 缺点是有一个#,显得不像真是的路径
<body>
  <div id="app">
    <a href="#/home">首页</a> 
    <a href="#/about">关于</a>
    <div class="router-view"> 初始内容 </div>
  </div>
  
  <script>
    // 获取router-view的DOM
    const routerViewEl = document.getElementsByClassName("router-view")[0];

    // 监听URL的改变. location 是一个全局变量
    window.addEventListener("hashchange", () => {
      switch (location.hash) {
        case "#/home":
          routerViewEl.innerHTML = "改成首页";
          break;
        case "#/about":
          routerViewEl.innerHTML = "改成关于";
          break;
        default:
          routerViewEl.innerHTML = "";
      }
    })
  </script>
</body>

默认我们的 url 是http://127.0.0.1:5500/监听hash的该改变.html; 当我们点击首页的时候,就会改成http://127.0.0.1:5500/监听hash的该改变.html#/home, 同时router-view div 中的内容也会改变;

HTML5 的 history
  • history 接口是 HTML5 新增的,它有六种模式改变 URL 而不刷新页面
  • replaceState:替换原来的路径;
  • pushState:使用新的路径;
  • popState:路径的回退;
  • go:向前或向后改变路径;
  • forward:向前改变路径;
  • back:向后改变路径;
<body>
  <div id="app">
    <a href="/home">首页</a>
    <a href="/about">关于</a>
    <div class="router-view"></div>
  </div>

  <script>
    // 1.获取router-view的DOM
    const routerViewEl = document.getElementsByClassName("router-view")[0];
    // 获取所有的a元素, 自己来监听a元素的改变
    const aEls = document.getElementsByTagName("a");
    for (let el of aEls) {
      el.addEventListener("click", e => {
        e.preventDefault();
        const href = el.getAttribute("href");
        history.pushState({}, "", href);
        urlChange();
      })
    }

    // 执行返回操作时, 依然来到urlChange
    window.addEventListener('popstate',urlChange);
    // 监听URL的改变
    function urlChange() {
      switch (location.pathname) { // 这里的 pathname 就是点击的 a 标签的 href 值
        case "/home":
          routerViewEl.innerHTML = "改成首页";
          break;
        case "/about":
          routerViewEl.innerHTML = "改成关于";
          break;
        default:
          routerViewEl.innerHTML = "";
      }
    }
  </script>
</body>

React-router

  • 目前前端流行的三大框架, 都有自己的路由实现:
  • Angular的ngRouter
  • React的react-router
  • Vue的vue-router
  • React Router的版本4开始,路由不再集中在一个包中进行管理了:
  • react-router是router的核心部分代码
  • react-router-dom是用于浏览器的;
  • react-router-native是用于原生应用的;
  • 目前我们使用最新的React Router版本是v5的版本:
  • 实际上v4的版本和v5的版本差异并不大;
  • 安装react-router:
  • 安装react-router-dom会自动帮助我们安装react-router的依赖;
    yarn add react-router-dom

基本使用

BrowserRouter、HashRouter
  • Router 中包含了对路径改变的监听,并且会将对应的路径传递给子组件
  • BrowserRouter使用 history 模式
  • HashRouter使用 hash 模式
Link 和 NavLink
  • 通常路径的跳转是使用 Link 组件,最终会被渲染成 a 元素
  • to属性: Link 中最重要的属性,用于设置跳转到的路径
  • NavLink 是在 Link 基础之上增加了一些样式属性,可以设置我们选中元素的样式
  • activeStyle:活跃(匹配)时的样式;<NavLink exact to="/" activeStyle={{color: "red", fontSize: "30px"}}>首页</NavLink>
  • activeClassName: 活跃时添加的 class, 默认情况下NavLink匹配成功时会动态添加一个名为 active 的 class, 所以我们也可以直接编写样式, 如果担心这个 class 在其他地方也使用了,出现样式的层叠,就可以使用 activeClassName 自己添加样式
  • exact: 精准匹配
a.active { // 设置活跃时的样式
  color: red;
  font-size: 30px;
}
a.link-active {
  color: yellow;
  font-size: 40px;
}
<NavLink exact to="/">首页</NavLink>
<NavLink to="/about" activeClassName="link-active">关于</NavLink>
Route
  • Route 用于匹配路径
  • path属性: 用于设置匹配到的路径
  • component 属性: 设置匹配到路径后,渲染的组件
  • exact: 精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件, 比如: Home 组件的 path 是'/', About 组件的 path 是'/about', 那么在匹配'/'的时候如果不是精准匹配会将 '/' 和'/about'都匹配出来,然后将两个组件都渲染到界面上
        <HashRouter>
          <Link to="/">首页</Link>
          <Link to="/about">关于</Link>
          <Link to="/profile">我的</Link>
          <Route exact path="/" component={Home}/>
          <Route exact path="/about" component={About}/>
          <Route exact path="/profile" component={Profile}/>
        </HashRouter>
  • 所有的Route 都只是占位,只有在匹配到的时候才在所占的位置显示, 如下,当匹配到'/', '/about','/profile'任一 path 时,会将对应的组件显示在<div> 哈哈哈 </div><div> 呵呵呵 </div>之间
 <HashRouter>
          <Link to="/">首页</Link>
          <Link to="/about">关于</Link>
          <Link to="/profile">我的</Link>
          <div> 哈哈哈 </div>
          <Route exact path="/" component={Home}/>
          <Route exact path="/about" component={About}/>
          <Route exact path="/profile" component={Profile}/>
          <div> 呵呵呵 </div>
</HashRouter>
Switch
  • 默认情况下路由会将匹配到的所有组件都会被渲染出来,比如下面,我们想匹配/about 路径,但是结果会将/:userid 和 NoMatch组件也匹配到,这是我们不希望的,我们希望再传入 userid 的时候显示 User 组件,在所有路由都没有匹配到的时候,显示 NoMatch 组件;这时候就可以使用 Switch帮我们解决这个问题了
  • Switch 包裹的路由,只要匹配到了第一个,那么后面的就不会再继续匹配了
<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="/:id" component={User} />
  <Route component={NoMatch} />
</Switch>
Redirect
  • Redirect 用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to 路径中, 比如我们在用户信息页中根据是否登录来判断显示内容,如果登录显示用户信息,如果没登录显示登录, 当用户未登录时,下面就会重定向到路径为/login 匹配的组件上
  render() {
    return this.state.isLogin ? (
      <div>
        <h2>User</h2>
        <h2>用户名: ****</h2>
      </div>
    ): <Redirect to="/login" />
  }
路由的嵌套
  • 在开发中,路由之间是存在嵌套关系的,假设 about 页面中有两个子页面,点击 tab 进行页面的切换, 那就需要在 about 中进行子路由的设置,设置子路由的 path 时需要带上父路由的 path,即 path 要写全
class App extends PureComponent {
  render() {
    return (
      <div>
        <HashRouter>
          <Link to="/">首页</Link>
          <Link to="/about">关于</Link>
          <Link to="/profile">我的</Link>
          <Route exact path="/" component={Home}/>
          <Route exact path="/about" component={About}/>
          <Route exact path="/profile" component={Profile}/>
        </HashRouter>
      <div>
     )
   }
}

export default class About extends PureComponent {
  render() {
    return (
      <div>
        {/* 这里默认展示企业历史*/}
        <NavLink exact to="/about" >企业历史</NavLink>
       {/*子路由的路径需要写全*/}
        <NavLink exact to="/about/culture" >企业文化</NavLink>
        <NavLink exact to="/about/contact" >联系我们</NavLink>
        <Switch>
          {/* 如果不加 exact 进行精准匹配,那么所有子路由的都能匹配到/about, 这样的话就只能展示AboutHisotry组件了*/}
          <Route exact path="/about" component={AboutHisotry}/>
          <Route path="/about/culture" component={AboutCulture}/>
          <Route path="/about/contact" component={AboutContact}/>
        </Switch> 
      </div>
    )
  }
}
手动路由跳转
  • 路由中我们不止可以通过Link 或 NavLink 进行跳转,也可以通过 JS 代码进行跳转, 通过 JS 代码跳转的前提是必须拿到 history 对象。
  • 获取 history 对象有两种方式
  • 如果该组件是通过路由直接跳转过来的,那么可以通过 props 直接获取 history、location、match 对象,这些属性是路由帮我传进来的
class About extends PureComponent {
  render() {
    return (
      <div>
        <NavLink exact to="/about" >企业历史</NavLink>
        <NavLink exact to="/about/culture" >企业文化</NavLink>
        <NavLink exact to="/about/contact" >联系我们</NavLink>
        <button onClick={e => this.jumpToJoin()}>加入我们</button>

        <Switch>
          <Route exact path="/about" component={AboutHisotry}/>
          <Route path="/about/culture" component={AboutCulture}/>
          <Route path="/about/contact" component={AboutContact}/>
          <Route path="/about/join" component={AboutJoin}/>
        </Switch> 
      </div>
    )
  }

  jumpToJoin() {
    this.props.history.push("/about/join");
  }
}
  • 如果该组件不是通过路由直接跳转过来的,就不能通过props 获取 histroy 等对象,这就需要我们使用高阶组件 withRouter 对我们的组件进行包裹,同时还需要将我们的高阶组件包裹在 Router 组件之中
参数传递
  • 路由中传递参数有三种方式
  • 动态路由的方式
  • search 传递参数
  • Link 中 to 传入对象
动态路由
  • 动态路由的概念指的是路由中的路径并不会固定
  • 比如/detail 的 path 对应一个组件 Detail
  • 如果我们将 path 在 Route 匹配时写成/detail/:id,那么/detail/abc、/detail/123 都可以匹配到该 Route,并且进行显示
{/* 设置路由匹配路径*/}
<Route exact path="/detail/:id" component={About}/>
{/* 传递参数 id*/}
<NavLink to={`/detail/${id}`} a>详情</NavLink>
{/* 取出传递的 id*/}
const id = this.props.match.params.id;
  • 这个匹配规则,我们就称之为动态路由
  • 通常情况下,使用动态路由可以为路由传递参数
  • 动态路由只能传递简单的字符串
search 传递参数
  • 我们在在路径后面跟上一个?,然后在?后面以 key=value 的形式拼接的字符串的传参方式称为 search 方式传递参数
    <Route path="/detail" component={Detail} />
    <NavLink to={/detail2?name={name}&age={age}} </NavLink>
    然后我们可以通过this.props.location.search取出传递的参数
    "?name=***&age=***", 这个传过来的参数是需要我们自己解析的,因为现在React-router推荐 to 的方式传递,不再推荐这种方式了,所以不再帮我们解析了。
Link 中使用 to 传递对象
  • Link 的 to 属性可以接收 string、object、func 三种类型的数据,当我们要通过 Router 传递对象时就可以给to 赋值一个对象, 这个对象主要有四个属性:
  • pathname: A string representing the path to link to
  • search: A string representation of query parameters.
  • hash: A hash to put in the URL, e.g. #a-hash.
  • state: State to persist to the location. 传递的对象赋值给 state 就可以了, 取的时候通过this.props.location.state.属性名就可以
<Route path="/detail" component={Detail} />

<NavLink to={{
      pathname: "/detail",
      state: {name:"zhangsan", age=18} 
}}> 详情3</NavLink>

const name = this.props.location.state.name; //取出传递的 name 值

pathname: A string representing the path to link to.
search: A string representation of query parameters.
hash: A hash to put in the URL, e.g. #a-hash.
state: State to persist to the location.

react-router-config 库

  • react-router-config 这个库,可以将所有的路由配置放到一个地方进行集中管理,而不是使用 Route 组件写的到处都是, 集中管理起来就方便修改和维护
安装
  • 首先我们要先安装这个库: yarn add react-router-config
使用
  • 创建一个专门管理 router 的文件夹 router, 然后创建index.js文件
  • 在 index.js 文件中实现如下, 每个 route对象都有以下几个属性:
  • path: /路径
  • exact: 是否精准匹配
  • component: 对应组件
  • routes: 子路由列表, 列表中的对象同样也是 route 对象
import Home from '../pages/home';
import About, { AboutHisotry, AboutCulture, AboutContact, AboutJoin } from '../pages/about';
import Profile from '../pages/profile';

const routes = [
  {
    path: "/",
    exact: true,
    component: Home
  },
  {
    path: "/about",
    component: About,
    routes: [
      {
        path: "/about",
        exact: true,
        component: AboutHisotry
      },
      {
        path: "/about/culture",
        component: AboutCulture
      },
      {
        path: "/about/contact",
        component: AboutContact
      },
    ]
  },
  {
    path: "/profile",
    component: Profile
  },
]
export default routes;
  • 在使用的地方导入 route 列表, 导入react-router-config的renderRoutes函数
import React, { PureComponent } from 'react';
import { renderRoutes } from 'react-router-config';
import routes from './router'; // 如果是 index.js 可以省略,默认导入的就是 index.js
class App extends PureComponent {
  render() {
    return (
      <div>
        <NavLink exact to="/" >首页</NavLink>
        <NavLink to="/about" >关于</NavLink>
        <NavLink to="/profile" >我的</NavLink>
        {/* 使用renderRoutes 渲染 routes*/}
        {renderRoutes(routes)}
      </div>
    )
  }
}

export default withRouter(App);
  • 如果有嵌套,我们同样调用renderRoutes函数渲染我们的子路由, 这里子路由的 routes 列表可以从 props.route.routes 中获取,renderRoutes函数会在props 中添加 route 属性,如果没有使用renderRoutes函数,props 中是不会有 route 这个属性的
import React, { PureComponent } from 'react'
import { NavLink } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';

export function AboutHisotry(props) {
  return <h2>企业成立于2000年, 拥有悠久的历史文化</h2>
}

export function AboutCulture(props) {
  return <h2>创新/发展/共赢</h2>
}

export function AboutContact(props) {
  return <h2>联系电话: 020-68888888</h2>
}

export default class About extends PureComponent {
  render() {
    return (
      <div>
        <NavLink exact to="/about" >企业历史</NavLink>
        <NavLink exact to="/about/culture" >企业文化</NavLink>
        <NavLink exact to="/about/contact" >联系我们</NavLink>

        {renderRoutes(this.props.route.routes)}
      </div>
    )
  }
}
  • react-router-config 还提供了一个matchRoutes函数,来帮我获取匹配到的route
    const branch = matchRoutes(this.props.route.routes, "/about");
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容