07-06-React-Router的单页应用

课程目标

  • 理解前端路由的作用;
  • 掌握 React-Router 各 API 使用细节;
  • 可根据项目需求,在 React 项目中,组织合理的路由方案。

课程内容

路由

  • 路由:根据不同的 url 规则,给用户展示不同的视图(页面);
  • 当应用变得复杂的时候,就需要分块的进行处理和展示,传统模式下,我们是把整个应用分成了多个页面,然后通过 url 进行连接。但是这种方式也有一些问题,每次切换页面都需要重新发送所有请求和渲染整个页面,不止性能上会有影响,同时也会导致整个 JavaScript 重新执行,丢失状态。

SPA

  • Single Page Application:单页面应用,整个应用只加载一个页面(入口页面),后续在与用户的交互过程中,通过 DOM 操作在这个单页上动态生成结构与内容。
优点
  • 有更好的用户体验(减少请求、渲染和页面跳转产生的等待与空白),页面切换快;
  • 重前端,数据和页面内容由异步请求(AJAX)+ DOM 操作来完成,前端处理更多的业务逻辑。
缺点
  • 首次进入慢;
  • 不利于 SEO。

SPA 的页面切换机制

  • 虽然 SPA 的内容都是在一个页面通过 JavaScript 动态处理的,但是还是需要根据需求在不同的情况下区分内容展示,如果仅仅只是依靠 JavaScript 内部机制去判断,逻辑会变得过于复杂,通过把 JavaScript 与 URL 进行结合的方式:JavaScript 根据 URL 的变化,来处理不同的逻辑,交互过程中只需要改变 URL 即可。这样把不同的 URL 与 JavaScript 对应的逻辑进行关联的方式就是路由,其本质上与后端路由的思想是一样的。

前端路由

  • 前端路由只是改变了 URL 或 URL 中的某一部分,但一定不会直接发送请求,可以认为仅仅只是改变了浏览器地址栏上的 URL 而已,JavaScript 通过各种手段处理这种 URL 的变化,然后通过 DOM 操作来动态的改变当前页面的结构;
  • URL 的变化不会直接发送 HTTP 请求;
  • 业务逻辑由前端 JavaScript 来完成。
目前前端路由的主要模式
  • 基于 URL Hash 的路由;
  • 基于 HTML5 History API 的路由。

React Router

  • 理解了路由的基本机制以后,也不需要重复造轮子,我们可以直接使用 React Router 库;
  • React Router 提供了多种不同环境下的路由库:web、native;
  • 官网:https://reactrouter.com/

基于 Web 的 React Router

  • 基于 web 的 React Router 为:react-router-dom;
  • 安装:npm i -S react-router-dom。

路由模式

BrowserRouter 组件 -- history
  • 基于 HTML5 History API 的路由组件。
HashRouter 组件 -- hash
  • 基于 URL Hash 的路由组件。
功能组件
  • Route 组件:
    • 匹配规则(v6 之前):
      • 默认模糊匹配,当前 URL 以该 path 为开始时,则匹配成功;
      • exact:精确匹配,URL===path || URL===path/;
      • strict:严格匹配,URL===path,要注意 strict 必须与精确匹配一起使用才生效;
      • 多路径匹配:通过数组实现;
      • 动态路由,见最下面。
    • 渲染视图:
      • component;
      • render。
    import { Route } from 'react-router-dom';
    import About from './views/About';
    import Hire from './views/Hire';
    import Home from './views/Home';
    /**
     * 匹配规则:
     * 1、默认情况-——模糊匹配,当前 URL 以该 path 为开始时,就能匹配:/index、/index/、/index/xxx;
     * 2、exact——精确匹配,URL===path || URL===path/;
     * 3、strict——严格匹配,URL===path,但是需要与 exact 一起使用才生效;
     * 4、多路径匹配——通过数组匹配。
     */
    
    function App() {
      // 当有值需要传递给对应路由组件时,可以通过 render 属性实现。
      const user = {
        name: 'yjw',
        age: 18
      }
      return (  
        <>
          <div>React Router Page</div>
          <Route exact path={['/', '/home', '/index']} component={Home} /> 
          <Route exact strict path='/about' component={About} />
          <Route 
            path='/hire' 
            render={() => {
              return <Hire user={user} />
            }} 
          />
        </>
      );
    }
    
    export default App;
    
    // react-router 6 之后的使用方式
    import { Routes, Route } from 'react-router-dom';
    import About from './views/About';
    import Hire from './views/Hire';
    import Home from './views/Home';
    
    function App() {
      return (  
        <>
          <Routes>
            {/* 默认 精确匹配,严格匹配 */}
            <Route path='home' element={<Home />} /> 
            <Route path='/about' element={<About />} />
            <Route path='/hire' element={<Hire />} />
          </Routes>
        </>
      );
    }
    
    export default App;
    
  • 链接组件:
    • Link;
    • NavLink:
      • activeClassName;
      • activeStyle;
      • isActive: function。
    // 可以使用 a 标签实现,但是页面每次都会刷新
    export default () => {
      return <>
        <a href='/home' >首页</a>
        <span> | </span>
        <a href='/about' >关于</a>
        <span> | </span>
        <a href='/hire' >加入</a>
      </>
    }
    
    import { Link, NavLink } from "react-router-dom"
    /**
     * 应用内链接:Link 或者 NavLink
     * 应用外链接:a 标签
     */
    /**
     * NavLink 用于导航的链接制作
     * - 当当前的 url 和 NavLink 的 to 属性匹配后,则会给当前的标签加一个选中状态,注:NavLink 默认情况下也是模糊匹配;
     * - activeClassName:当前项被选中后的 className,默认为 active;
     * - activeStyle:当前项被选中后的 style;
     * - isActive:function,返回一个 Boolean 值,表示该标签的 class、style 始终是 active 状态或者 非active 状态。
     */
    export default () => {
      return <div>
        <NavLink to='/' activeClassName='homeActive'>首页</NavLink>
        <span> | </span>
        <NavLink to='/about' activeStyle={{color: 'red'}}>关于</NavLink>
        <span> | </span>
        <NavLink to='/hire' isActive={()=>{return true}} activeStyle={{color: 'yellow'}}>加入</NavLink>
        <span> | </span>
        <a href='https://www.baidu.com' >百度</a>
      </div>
    }
    
  • Switch 组件:只匹配一个路径;
  • Redirect 组件:当输入的 url 不合法时,可以重定向到 404:
    • form 属性;
    • to 属性。
    import { Route, Switch, Redirect } from 'react-router-dom';
    import Nav from './Nav';
    import About from './views/About';
    import Hire from './views/Hire';
    import Home from './views/Home';
    import View404 from './views/404.js';
    
    function App() {
      // 当有值需要传递给对应路由组件时,可以通过 render 属性实现。
      const user = {
        name: 'yjw',
        age: 18
      }
      return (  
        <>
          <div>React Router Page</div>
          <Nav />
          <Switch>
            <Route exact path={['/', '/home', '/index']} component={Home} /> 
            <Route exact path='/about' component={About} />
            <Route 
              path='/about/join' 
              render={() => {
                return <Hire user={user} />
              }} 
            />
            <Route path='/404' component={View404} />
            {/* 当路径找不到时,显示404页面,并且将 url 显示为404 */}
            <Redirect to='/404' />
          </Switch>
        </>
      );
    }
    
    export default App;
    

路由参数

  • 通过 props,可以获取 history、location、match 和 staticContext。
history
  • action:“PUSH” || “POP” || “REPLACE”;
    • “PUSH”:应用内通过连接跳转到当前视图的,或者通过 push 方法跳转到当前视图;
    • “POP”:直接输入地址跳转到当前应用,或者从外部链接跳转进来的;
    • “REPLACE”:通过重定向跳转或者通过 replace 方法跳转;
  • go:function,go(n)——跳转历史记录n步;
  • goBack:function,goBack()——返回历史记录上一步;
  • goForward:function,goForward()——前进历史记录下一步;
  • length:当前源在历史记录中记录的条目数;
  • push:function,push(path[,state])——跳转视图,向历史记录中,添加新的一条记录,从而影响视图;state 的值其实就是修改 location 下面的 state 的值,同 replace 方法中的 state;
  • replace:function,replace(path[,state])——跳转视图,替换掉历史记录中当前这条;
  • block:当离开当前组件时,会弹窗提示;全局函数,如果只需要在当前组件进行弹窗提示,在当前组件即将卸载时,调用该方法的返回值进行移除;
  • createHref:function,createHref(location),当 url 比较复杂时,比如含有参数和hash,这时可以使用该方法,返回一个 url 地址,但是需要再调用 push、replace 来进行跳转;
  • listen:监听 url 跳转,如果跳转了会打印 location和action,同样是全局函数。
location:
  • hash:hash 值,url 中 # 后面的内容;
  • pathname:当前的 url,不包含参数和hash;
  • search:当前的 search 值,? 后面的内容;
  • state:undefined、push 或 replace 传递的信息。
match:匹配信息
  • isExact:boolean,和 Route 中配置没有关系,取决于当前 path 和 url 是否能精确匹配;
  • params:{} 动态路由的参数;
  • path:和 pathname 不是一个概念,这里的 path 是当前 route 的 path 值;
  • url:当前 url 中,被当前 path 匹配成功的部分。

路由信息获取

高阶路由 - withRouter
  • 非路由组件获取路由信息
    import { NavLink, withRouter } from "react-router-dom";
    
    function SubNav(props) {
      console.log('SubNav: ', props);
      return (  
        <div className='sub-nav'>
          <NavLink 
            isActive={(...args) => {
              console.log('arg: ', args)
              return true;
            }}
            to='/list/all'
          >全部</NavLink>
          <NavLink to='/list/good'>精华</NavLink>
          <NavLink to='/list/share'>分享</NavLink>
          <NavLink to='/list/ask'>问答</NavLink>
        </div>
      );
    }
    
    const Nav = withRouter(SubNav);
    export default Nav;
    
路由 Hooks,v5 版本之后引入了 Hooks。
  • useLocation();
  • useHistory();
  • useRouteMatch();
  • useParams():获取动态路由的参数信息。
  • 以下为完整练习代码:
    // App.js
    import { Route, Switch, Redirect } from 'react-router-dom';
    import Nav from './Nav';
    import About from './views/About';
    import Hire from './views/Hire';
    import Home from './views/Home';
    import View404 from './views/404.js';
    import List from './views/list/List';
    import './index.css';
    
    const types = ['good', 'good', 'share', 'ask'];
    
    function App() {
      // 当有值需要传递给对应路由组件时,可以通过 render 属性实现。
      const user = {
        name: 'yjw',
        age: 18
      }
      return (  
        <div className="wrap">
          <div>React Router Page</div>
          <Nav />
          <Switch>
            <Route path={['/home', '/index']} component={Home} /> 
            <Route path='/about' component={About} />
            <Route 
              path='/join' 
              render={() => {
                return <Hire user={user} />
              }} 
            />
            {/* 
              component 自带路由参数,通过 render 传递路由参数时需要手动将参数传递过去 
              - 动态路由: :代表动态路由,: 后面代表的是名字
            */}
            {/* <Route 
              // path={['/list', '/list/:type', '/list/:type/:page']}
              path='/list/:type?/:page?'
              render={ (props) => {
                return <List {...props}/>
              }}
              exact
            /> */}
            <Route 
              path='/list/:type?/:page?'
              exact
              render={({match}) => {
                console.log('route-> ', match)
                const {type='good', page='1'} = match.params;
                if(types.includes(type) && String(parseInt(page))===page && parseInt(page)>0) {
                  return <List />
                } else {
                  return <Redirect to="/404" />
                }
              }}
            />
            <Route path='/404' component={View404} />
            <Redirect to='/404' />
          </Switch>
        </div>
      );
    }
    /**
     * list 的路径问题:
     * - /list
     * - /list/分类
     * - list/分类/页码
     */
    
    export default App;
    
    // Nav.js
    import { Link, NavLink } from "react-router-dom"
    /**
     * 应用内链接:Link 或者 NavLink
     * 应用外链接:a 标签
     */
    /**
     * NavLink 用于导航的链接制作
     * - 当当前的 url 和 NavLink 的 to 属性匹配后,则会给当前的标签加一个选中状态,注:NavLink 默认情况下也是模糊匹配;
     * - activeClassName:当前项被选中后的 className,默认为 active;
     * - activeStyle:当前项被选中后的 style;
     * - isActive:function,返回一个 Boolean 值,表示该标签的 class、style 始终是 active 状态或者 非active 状态。
     */
    export default () => {
      return <div className='nav'>
        <NavLink to='/home' >首页</NavLink>
        <span> | </span>
        <NavLink to='/about' >关于</NavLink>
        <span> | </span>
        <NavLink to='/join' >加入</NavLink>
        <span> | </span>
        <NavLink to='/list' >产品列表</NavLink>
      </div>
    }
    
    // List.js
    import ListList from "./ListList";
    import Pagination from "./Pagination";
    import SubNav from "./SubNav";
    
    function List(props) {
      return (  
        <>
          <h3>List</h3>
          <SubNav />
          <ListList />
          <Pagination />
        </>
      );
    }
    
    export default List;
    
    // SubNav.js
    import { NavLink, useParams } from "react-router-dom";
    
    function SubNav(props) {
      const { type='good' } = useParams();
      return (  
        <div className='sub-nav'>
          <NavLink to='/list/good'>精华</NavLink>
          <NavLink to='/list/share'>分享</NavLink>
          <NavLink to='/list/ask'>问答</NavLink>
        </div>
      );
    }
    
    export default SubNav;
    
    // Pagination.js
    import { useParams } from "react-router";
    import { Link } from "react-router-dom";
    import data from './data';
    const limit = 6;
    
    function Pagination() {
      const { type='good', page='1' } = useParams();
      const nowData = data[type];
      const pageLen = Math.ceil(nowData.length/limit);
    
      const renderPage = () => {
        const inner = []
        for (let i = 1; i <= pageLen; i++) {
          if(pageLen === Number(page)) {
            inner.push(<span key={i}>{i}</span>)
          } else {
            inner.push(<Link to={`/list/${type}/${i}`} key={i}>{i}</Link>)
          }
        }
        return inner;
      }
      return (  
        <div>
          {renderPage()}
        </div>
      );
    }
    
    export default Pagination;
    
    // ListList.js
    import { useParams } from "react-router";
    import data from './data';
    const limit = 6;
    
    /**
     * 求页数
     * 每页的第一条:(page-1) * 6
     */
    function ListList() {
      const { type='good', page='1' } = useParams();
      const nowPage = Number(page);
      const start = (nowPage - 1) * limit;
      const end = nowPage * limit;
      const nowData = data[type]?.filter((item, index)=>(index>=start && index<=end));
      
      return ( 
        <div>
          <ul>
            {nowData?.map(d => {
              return <li key={d.id}>{d.title}</li>
            })}
          </ul>
        </div>
      );
    }
    
    export default ListList;
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,041评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 6,876评论 0 2
  • 年纪越大,人的反应就越迟钝,脑子就越不好使,计划稍有变化,就容易手忙脚乱,乱了方寸。 “玩坏了”也是如此,不但会乱...
    玩坏了阅读 2,127评论 2 1
  • 感动 我在你的眼里的样子,就是你的样子。 相互内化 没有绝对的善恶 有因必有果 当你以自己的价值观幸福感去要求其他...
    周粥粥叭阅读 1,635评论 1 5
  • 昨天考过了阿里规范,心里舒坦了好多,敲代码也犹如神助。早早完成工作回家喽
    常亚星阅读 3,034评论 0 1