07-06-React-Router的單頁(yè)應(yīng)用

課程目標(biāo)

  • 理解前端路由的作用;
  • 掌握 React-Router 各 API 使用細(xì)節(jié);
  • 可根據(jù)項(xiàng)目需求,在 React 項(xiàng)目中,組織合理的路由方案。

課程內(nèi)容

路由

  • 路由:根據(jù)不同的 url 規(guī)則,給用戶展示不同的視圖(頁(yè)面);
  • 當(dāng)應(yīng)用變得復(fù)雜的時(shí)候,就需要分塊的進(jìn)行處理和展示,傳統(tǒng)模式下,我們是把整個(gè)應(yīng)用分成了多個(gè)頁(yè)面,然后通過(guò) url 進(jìn)行連接。但是這種方式也有一些問(wèn)題,每次切換頁(yè)面都需要重新發(fā)送所有請(qǐng)求和渲染整個(gè)頁(yè)面,不止性能上會(huì)有影響,同時(shí)也會(huì)導(dǎo)致整個(gè) JavaScript 重新執(zhí)行,丟失狀態(tài)。

SPA

  • Single Page Application:?jiǎn)雾?yè)面應(yīng)用,整個(gè)應(yīng)用只加載一個(gè)頁(yè)面(入口頁(yè)面),后續(xù)在與用戶的交互過(guò)程中,通過(guò) DOM 操作在這個(gè)單頁(yè)上動(dòng)態(tài)生成結(jié)構(gòu)與內(nèi)容。
優(yōu)點(diǎn)
  • 有更好的用戶體驗(yàn)(減少請(qǐng)求、渲染和頁(yè)面跳轉(zhuǎn)產(chǎn)生的等待與空白),頁(yè)面切換快;
  • 重前端,數(shù)據(jù)和頁(yè)面內(nèi)容由異步請(qǐng)求(AJAX)+ DOM 操作來(lái)完成,前端處理更多的業(yè)務(wù)邏輯。
缺點(diǎn)
  • 首次進(jìn)入慢;
  • 不利于 SEO。

SPA 的頁(yè)面切換機(jī)制

  • 雖然 SPA 的內(nèi)容都是在一個(gè)頁(yè)面通過(guò) JavaScript 動(dòng)態(tài)處理的,但是還是需要根據(jù)需求在不同的情況下區(qū)分內(nèi)容展示,如果僅僅只是依靠 JavaScript 內(nèi)部機(jī)制去判斷,邏輯會(huì)變得過(guò)于復(fù)雜,通過(guò)把 JavaScript 與 URL 進(jìn)行結(jié)合的方式:JavaScript 根據(jù) URL 的變化,來(lái)處理不同的邏輯,交互過(guò)程中只需要改變 URL 即可。這樣把不同的 URL 與 JavaScript 對(duì)應(yīng)的邏輯進(jìn)行關(guān)聯(lián)的方式就是路由,其本質(zhì)上與后端路由的思想是一樣的。

前端路由

  • 前端路由只是改變了 URL 或 URL 中的某一部分,但一定不會(huì)直接發(fā)送請(qǐng)求,可以認(rèn)為僅僅只是改變了瀏覽器地址欄上的 URL 而已,JavaScript 通過(guò)各種手段處理這種 URL 的變化,然后通過(guò) DOM 操作來(lái)動(dòng)態(tài)的改變當(dāng)前頁(yè)面的結(jié)構(gòu);
  • URL 的變化不會(huì)直接發(fā)送 HTTP 請(qǐng)求;
  • 業(yè)務(wù)邏輯由前端 JavaScript 來(lái)完成。
目前前端路由的主要模式
  • 基于 URL Hash 的路由;
  • 基于 HTML5 History API 的路由。

React Router

  • 理解了路由的基本機(jī)制以后,也不需要重復(fù)造輪子,我們可以直接使用 React Router 庫(kù);
  • React Router 提供了多種不同環(huán)境下的路由庫(kù):web、native;
  • 官網(wǎng):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 組件:
    • 匹配規(guī)則(v6 之前):
      • 默認(rèn)模糊匹配,當(dāng)前 URL 以該 path 為開(kāi)始時(shí),則匹配成功;
      • exact:精確匹配,URL===path || URL===path/;
      • strict:嚴(yán)格匹配,URL===path,要注意 strict 必須與精確匹配一起使用才生效;
      • 多路徑匹配:通過(guò)數(shù)組實(shí)現(xiàn);
      • 動(dòng)態(tài)路由,見(jiàn)最下面。
    • 渲染視圖:
      • component;
      • render。
    import { Route } from 'react-router-dom';
    import About from './views/About';
    import Hire from './views/Hire';
    import Home from './views/Home';
    /**
     * 匹配規(guī)則:
     * 1、默認(rèn)情況-——模糊匹配,當(dāng)前 URL 以該 path 為開(kāi)始時(shí),就能匹配:/index、/index/、/index/xxx;
     * 2、exact——精確匹配,URL===path || URL===path/;
     * 3、strict——嚴(yán)格匹配,URL===path,但是需要與 exact 一起使用才生效;
     * 4、多路徑匹配——通過(guò)數(shù)組匹配。
     */
    
    function App() {
      // 當(dāng)有值需要傳遞給對(duì)應(yīng)路由組件時(shí),可以通過(guò) render 屬性實(shí)現(xiàn)。
      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>
            {/* 默認(rèn) 精確匹配,嚴(yán)格匹配 */}
            <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 標(biāo)簽實(shí)現(xiàn),但是頁(yè)面每次都會(huì)刷新
    export default () => {
      return <>
        <a href='/home' >首頁(yè)</a>
        <span> | </span>
        <a href='/about' >關(guān)于</a>
        <span> | </span>
        <a href='/hire' >加入</a>
      </>
    }
    
    import { Link, NavLink } from "react-router-dom"
    /**
     * 應(yīng)用內(nèi)鏈接:Link 或者 NavLink
     * 應(yīng)用外鏈接:a 標(biāo)簽
     */
    /**
     * NavLink 用于導(dǎo)航的鏈接制作
     * - 當(dāng)當(dāng)前的 url 和 NavLink 的 to 屬性匹配后,則會(huì)給當(dāng)前的標(biāo)簽加一個(gè)選中狀態(tài),注:NavLink 默認(rèn)情況下也是模糊匹配;
     * - activeClassName:當(dāng)前項(xiàng)被選中后的 className,默認(rèn)為 active;
     * - activeStyle:當(dāng)前項(xiàng)被選中后的 style;
     * - isActive:function,返回一個(gè) Boolean 值,表示該標(biāo)簽的 class、style 始終是 active 狀態(tài)或者 非active 狀態(tài)。
     */
    export default () => {
      return <div>
        <NavLink to='/' activeClassName='homeActive'>首頁(yè)</NavLink>
        <span> | </span>
        <NavLink to='/about' activeStyle={{color: 'red'}}>關(guān)于</NavLink>
        <span> | </span>
        <NavLink to='/hire' isActive={()=>{return true}} activeStyle={{color: 'yellow'}}>加入</NavLink>
        <span> | </span>
        <a  >百度</a>
      </div>
    }
    
  • Switch 組件:只匹配一個(gè)路徑;
  • Redirect 組件:當(dāng)輸入的 url 不合法時(shí),可以重定向到 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() {
      // 當(dāng)有值需要傳遞給對(duì)應(yīng)路由組件時(shí),可以通過(guò) render 屬性實(shí)現(xiàn)。
      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} />
            {/* 當(dāng)路徑找不到時(shí),顯示404頁(yè)面,并且將 url 顯示為404 */}
            <Redirect to='/404' />
          </Switch>
        </>
      );
    }
    
    export default App;
    

路由參數(shù)

  • 通過(guò) props,可以獲取 history、location、match 和 staticContext。
history
  • action:“PUSH” || “POP” || “REPLACE”;
    • “PUSH”:應(yīng)用內(nèi)通過(guò)連接跳轉(zhuǎn)到當(dāng)前視圖的,或者通過(guò) push 方法跳轉(zhuǎn)到當(dāng)前視圖;
    • “POP”:直接輸入地址跳轉(zhuǎn)到當(dāng)前應(yīng)用,或者從外部鏈接跳轉(zhuǎn)進(jìn)來(lái)的;
    • “REPLACE”:通過(guò)重定向跳轉(zhuǎn)或者通過(guò) replace 方法跳轉(zhuǎn);
  • go:function,go(n)——跳轉(zhuǎn)歷史記錄n步;
  • goBack:function,goBack()——返回歷史記錄上一步;
  • goForward:function,goForward()——前進(jìn)歷史記錄下一步;
  • length:當(dāng)前源在歷史記錄中記錄的條目數(shù);
  • push:function,push(path[,state])——跳轉(zhuǎn)視圖,向歷史記錄中,添加新的一條記錄,從而影響視圖;state 的值其實(shí)就是修改 location 下面的 state 的值,同 replace 方法中的 state;
  • replace:function,replace(path[,state])——跳轉(zhuǎn)視圖,替換掉歷史記錄中當(dāng)前這條;
  • block:當(dāng)離開(kāi)當(dāng)前組件時(shí),會(huì)彈窗提示;全局函數(shù),如果只需要在當(dāng)前組件進(jìn)行彈窗提示,在當(dāng)前組件即將卸載時(shí),調(diào)用該方法的返回值進(jìn)行移除;
  • createHref:function,createHref(location),當(dāng) url 比較復(fù)雜時(shí),比如含有參數(shù)和hash,這時(shí)可以使用該方法,返回一個(gè) url 地址,但是需要再調(diào)用 push、replace 來(lái)進(jìn)行跳轉(zhuǎn);
  • listen:監(jiān)聽(tīng) url 跳轉(zhuǎn),如果跳轉(zhuǎn)了會(huì)打印 location和action,同樣是全局函數(shù)。
location:
  • hash:hash 值,url 中 # 后面的內(nèi)容;
  • pathname:當(dāng)前的 url,不包含參數(shù)和hash;
  • search:當(dāng)前的 search 值,? 后面的內(nèi)容;
  • state:undefined、push 或 replace 傳遞的信息。
match:匹配信息
  • isExact:boolean,和 Route 中配置沒(méi)有關(guān)系,取決于當(dāng)前 path 和 url 是否能精確匹配;
  • params:{} 動(dòng)態(tài)路由的參數(shù);
  • path:和 pathname 不是一個(gè)概念,這里的 path 是當(dāng)前 route 的 path 值;
  • url:當(dāng)前 url 中,被當(dāng)前 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'>問(wèn)答</NavLink>
        </div>
      );
    }
    
    const Nav = withRouter(SubNav);
    export default Nav;
    
路由 Hooks,v5 版本之后引入了 Hooks。
  • useLocation();
  • useHistory();
  • useRouteMatch();
  • useParams():獲取動(dòng)態(tài)路由的參數(shù)信息。
  • 以下為完整練習(xí)代碼:
    // 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() {
      // 當(dāng)有值需要傳遞給對(duì)應(yīng)路由組件時(shí),可以通過(guò) render 屬性實(shí)現(xiàn)。
      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 自帶路由參數(shù),通過(guò) render 傳遞路由參數(shù)時(shí)需要手動(dòng)將參數(shù)傳遞過(guò)去 
              - 動(dòng)態(tài)路由: :代表動(dòng)態(tài)路由,: 后面代表的是名字
            */}
            {/* <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 的路徑問(wèn)題:
     * - /list
     * - /list/分類
     * - list/分類/頁(yè)碼
     */
    
    export default App;
    
    // Nav.js
    import { Link, NavLink } from "react-router-dom"
    /**
     * 應(yīng)用內(nèi)鏈接:Link 或者 NavLink
     * 應(yīng)用外鏈接:a 標(biāo)簽
     */
    /**
     * NavLink 用于導(dǎo)航的鏈接制作
     * - 當(dāng)當(dāng)前的 url 和 NavLink 的 to 屬性匹配后,則會(huì)給當(dāng)前的標(biāo)簽加一個(gè)選中狀態(tài),注:NavLink 默認(rèn)情況下也是模糊匹配;
     * - activeClassName:當(dāng)前項(xiàng)被選中后的 className,默認(rèn)為 active;
     * - activeStyle:當(dāng)前項(xiàng)被選中后的 style;
     * - isActive:function,返回一個(gè) Boolean 值,表示該標(biāo)簽的 class、style 始終是 active 狀態(tài)或者 非active 狀態(tài)。
     */
    export default () => {
      return <div className='nav'>
        <NavLink to='/home' >首頁(yè)</NavLink>
        <span> | </span>
        <NavLink to='/about' >關(guān)于</NavLink>
        <span> | </span>
        <NavLink to='/join' >加入</NavLink>
        <span> | </span>
        <NavLink to='/list' >產(chǎn)品列表</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'>問(wèn)答</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;
    
    /**
     * 求頁(yè)數(shù)
     * 每頁(yè)的第一條:(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;
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者,不喜歡去冒險(xiǎn),但是人生放棄了冒險(xiǎn),也就放棄了無(wú)數(shù)的可能。 ...
    yichen大刀閱讀 7,866評(píng)論 0 4
  • 公元:2019年11月28日19時(shí)42分農(nóng)歷:二零一九年 十一月 初三日 戌時(shí)干支:己亥乙亥己巳甲戌當(dāng)月節(jié)氣:立冬...
    石放閱讀 7,454評(píng)論 0 2
  • 年紀(jì)越大,人的反應(yīng)就越遲鈍,腦子就越不好使,計(jì)劃稍有變化,就容易手忙腳亂,亂了方寸。 “玩壞了”也是如此,不但會(huì)亂...
    玩壞了閱讀 2,343評(píng)論 2 1
  • 感動(dòng) 我在你的眼里的樣子,就是你的樣子。 相互內(nèi)化 沒(méi)有絕對(duì)的善惡 有因必有果 當(dāng)你以自己的價(jià)值觀幸福感去要求其他...
    周粥粥叭閱讀 1,740評(píng)論 1 5
  • 昨天考過(guò)了阿里規(guī)范,心里舒坦了好多,敲代碼也猶如神助。早早完成工作回家嘍
    常亞星閱讀 3,258評(píng)論 0 1

友情鏈接更多精彩內(nèi)容