React(四)——React 路由(react-router-dom)

目錄

1.路由

1.1SPA

1.2SPA 的頁面切換機制

1.3後端路由與前端路由

1.3.1後端路由

1.3.2前端路由

1.4React.js 中的路由

2.React Router

2.1基於 Web 的 React Router

2.1.1安裝

2.1.2概覽

3.基礎

3.1應用場景(一)——頁面切換

3.1.1Router 組件

3.1.2Route 組件

3.1.3Link 組件

3.2應用場景(二)——商品主頁和詳情頁展示

3.2.1狀態提升

3.2.2傳遞 props

3.2.3渲染列表

3.2.4動態路由

3.2.5路由組件

3.3應用場景(三)——價格排序

3.3.1通過 JavaScript 實現排序切換

3.3.2通過路由實現排序切換

3.3.3擴展

3.4應用場景(四)——頁面頂部導航高亮

3.4.1NavLink 組件

3.5應用場景(五)——404頁面

3.5.1Switch 組件

3.6應用場景(六)——購物車

3.6.1Redirect 組件

3.7應用場景(七)——分頁組件

3.7.1withRouter()方法


1.路由

當應用變得複雜的時候,就需要分塊的進行處理和展示,傳統模式下,我們是把整個應用分成了多個頁面,然後通過 URL 進行連接。但是這種方式也有一些問題,每次切換頁面都需要重新發送所有請求和渲染整個頁面,不止性能上會有影響,同時也會導致整個 JavaScript 重新執行,丟失狀態。

傳統路由基於後端:瀏覽器——》發送http請求——》url——》分析URL——》返回不同html代碼——》瀏覽器得到返回數據——》據不同數據類型進行不同處理(根據2進制頭部信息)

1.1SPA

Single Page Application : 單頁面應用,整個應用只加載一個頁面(入口頁面),後續在與用戶的交互過程中,通過 DOM 操作在這個單頁上動態生成結構和內容。

優點:

  • 有更好的用戶體驗(減少請求和渲染和頁面跳轉產生的等待與空白),頁面切換快

  • 重前端,數據和頁面內容由異步請求(AJAX)+ DOM 操作來完成,前端處理更多的業務邏輯

缺點:

  • 首屏處理慢

  • 不利於 SEO

1.2SPA 的頁面切換機制

雖然 SPA 的內容都是在一個頁面通過 JavaScript 動態處理的,但是還是需要根據需求在不同的情況下分內容展示,如果僅僅只是依靠 JavaScript 內部機制去判斷,邏輯會變得過於複雜,通過把 JavaScriptURL 進行結合的方式: JavaScript 根據 URL 的變化,來處理不同的邏輯,交互過程中只需要改變 URL 即可。這樣把不同 URLJavaScript 對應的邏輯進行關聯的方式就是路由,其本質上與後端路由的思想是一樣的。

1.3後端路由與前端路由

後端路由與前端路由在本質上是類似的,都是把不同的 URL 與某個操作進行關聯綁定,得到不一樣的結果

1.3.1後端路由

通過 HTTP 把請求發送到後端服務器,後端服務器接收到請求以後根據不同的請求 URL 來執行不同的操作,返回處理好的數據(JSON、HTML、JS 代碼、CSS、圖像……)

  • 需要發送 HTTP 請求

  • 業務邏輯由後端處理,返回處理後的結果給前端(瀏覽器)

1.3.2前端路由

前端路由只是改變了 URLURL 中的某一部分,但一定不會直接發送請求,可以認爲僅僅只是改變了瀏覽器地址欄上的 URL 而已,JavaScript 通過各種手段處理這種 URL 的變化,然後通過 DOM 操作動態的改變當前頁面的結構。

  • URL 的變化不會直接發送 HTTP 請求
  • 業務邏輯由前端 JavaScript 來完成

目前前端路由主要的模式

  • 基於 URL Hash 的路由
  • 基於 HTML5 History API 的路由

1.3.2.1URL Hash

通過修改 URLHash 值來改變 URLHash 的變化是不會發送請求的,同時 JavaScript 通過監聽 hashchange 事件來動態處理邏輯和頁面渲染。

優點

兼容性好

缺點

URL 不美觀,SEO 不友好

1.3.2.2HTML5 History API

封裝一個函數,該函數通過 HTML5 History 提供的 API 來動態改變 URL ,這種方式也不會發送請求,然後同時根據要改變的目標 URL 來處理邏輯和頁面渲染

URL Hash 模式類似 Vue 中的數據攔截機制

HTML5 History API 模式類似 React.js 中的 setState

1.4React.js 中的路由

React.js 路由的基本思想就是,把不同的 URL 與 指定的某些 React.js 組件進行關聯,不同的 URL 渲染顯示不同的組件,其它框架(如:vue、angular) 都是一樣的思想

2.React Router

理解了路由基本機制以後,也不需要重複造輪子,我們可以直接使用 React Router

https://reacttraining.com/react-router/

React Router 提供了多種不同環境下的路由庫

  • web
  • native

2.1基於 WebReact Router

基於 webReact Router 爲:react-router-dom

2.1.1安裝

npm i -S react-router-dom

2.1.2概覽

react-router-dom 的核心是組件,它提供了一系列的組件,如:

  • Router 組件
  • BrowserRouter 組件
  • HashRouter 組件
  • Route 組件
  • Link 組件
  • NavLink 組件
  • Switch 組件
  • Redirect 組件

以及其它一些 API,來完成路由的功能

3.基礎

3.1應用場景(一)——頁面切換

假設當前應用有兩個頁面,對應的 URL 與 頁面關係

/       :   首頁
/about  :   關於我們

3.1.1Router 組件

如果我們希望頁面中某個部分的內容需要根據 URL 來動態顯示,需要用到 Router 組件 ,該組件是一個容器組件只需要用它包裹 URL 對應的根組件即可。用於確定那個範圍的組件收到路由控制。

react-router-dom 爲我們提供了幾個基於不同模式的 router 子組件

  • BrowserRouter 組件
  • HashRouter 組件
  • MemoryRouter 組件
  • NativeRouter 組件
  • StaticRouter 組件

3.1.1.1BrowserRouter 組件

基於 HTML5 History API 的路由組件,低版本瀏覽器可能不支持

3.1.1.2HashRouter 組件

基於 URL Hash 的路由組件,路徑中包含了#

BrowserRouter組件和HashRouter組件區別:

https://www.cnblogs.com/flamestudio/p/11965991.html

css

ul {
    margin: 0;
    padding: 0;
}

li {
    list-style: none;
}

.item-list li {
    padding: 10px;
    display: flex;
    justify-content: space-between;
    height: 30px;
    line-height: 30px;
    border-bottom: 1px dotted #333;
}
.item-list li.head {
    font-weight: bold;
}
.item-list li span {
    min-width: 200px;
}

.pagination {
    margin: 10px;
}
.pagination a {
    margin: 0 5px;
    display: inline-block;
    width: 30px;
    height: 30px;
    background-color: #f4f4f5;
    line-height: 30px;
    text-align: center;
    cursor: pointer;
    color: #606266;
    text-decoration: none;
}
.pagination a:hover {
    color: #409eff;
}
.pagination a.active {
    background-color: #409eff;
    color: #fff;
    cursor: text;
}
.pagination .goto {
    margin: 0 5px;
    box-sizing: border-box;
    width: 80px;
    height: 30px;
    border: 1px solid #dcdfe6;
    outline: none;
    text-align: center;
    vertical-align: top;
}

App.js

import React from 'react';

import {BrowserRouter as Router} from 'react-router-dom';
// import {HashRouter as Router} from 'react-router-dom';

import BaseApp from './BaseApp/Index';

function App() {
    return (
        <div>
            <h1>React-Router</h1>
            <hr/>

            <Router>
                <BaseApp />
            </Router>
        </div>
    );
}

export default App;
import {BrowserRouter as Router} from 'react-router-dom'

導入 BrowserRouter 組件,並命名 Router 別名,方便使用

<Router>
	  <BaseApp />
</Router>

 只對頁面中的 BaseApp 組件使用路由

./BaseApp/Index.js

import React from 'react';
import {Route, Link} from 'react-router-dom';

import Home from './Home';
import About from './About';

export default class BaseApp extends React.Component {

    render() {
        return(
            <div>
                <h2>路由基礎使用</h2>

                <nav>
                    <Link to="/">Home</Link>
                    <span> | </span>
                    <Link to="/about">About</Link>
                </nav>
                <br/>

                <Route exact path='/' component={Home} />
                <Route path='/about' component={About} />
            </div>
        );
    }

}
import {Route} from 'react-router-dom';

3.1.2Route 組件

注意這個組件是沒有字母 r 的,通過該組件來設置應用單個路由信息Route 組件所在的區域就是就是URL 與當前 Route 設置的 path 屬性匹配的時候,後面 component 將要顯示的區域

<Route path='/' component={Home} />

URL 爲:'/' 的時候,組件 Home 將顯示在這裏

exact

exact 屬性表示路由使用 精確匹配模式,非 exact 模式下 '/' 匹配所有以 '/' 開頭的路由

3.1.3Link 組件

Link 組件用來處理 a 鏈接 類似的功能(它會在頁面中生成一個 a 標籤),但設置這裏需要注意的,用戶點擊時react-router-dom 攔截了實際 a 標籤的默認動作,然後根據所有使用的路由模式(Hash 或者 HTML5)來進行處理,改變了 URL,但不會發生請求,同時根據 Route 中的設置把對應的組件顯示在指定的位置

to 屬性

to 屬性類似 a 標籤中的 href

場景一完整案例代碼:

App.js:

import React from 'react';
import './App.css';

import BaseRouter from './components/BaseRouter';
// 導入 BrowserRouter 組件,並命名 Router 別名,方便使用
import { BrowserRouter as Router} from 'react-router-dom';

function App() {
  return (
    <div className="App">
      <h1>React-Router</h1>
      <hr />
      <Router>
        <BaseRouter />
      </Router>
    </div>
  );
}

export default App;

BaseRouter.js:

import React from 'react';

import RouterSwitch from './RouterSwitch';
class BaseRouter extends React.Component{
    render(){
        return(
            <div>
                {/* 場景一:頁面切換 */}
                <RouterSwitch />
            </div>
        );
    }
}

export default BaseRouter;

 RouterSwitch.js:**

import React from 'react';
import { Route, Link } from 'react-router-dom';

import Home from './Home';
import About from './About';
/**
 * 場景一:使用路由無刷新切換頁面
 */
class RouterSwitch extends React.Component {
    render() {
        return (
            <div>
                {/* <Link>路由組件: 相當於a標籤功能;to屬性相當於a標籤的href屬性;攔截了a標籤中點擊後刷新跳轉*/}
                <nav>
                    <Link to="/">首頁</Link>
                    <span> | </span>
                    <Link to="/about">關於我們</Link>
                </nav>
                <br/>
                {/* Route 組件:用於設置路由信息,path屬性匹配路徑;component設置要顯示的組件;exact表示精確匹配,非 exact 模式下 '/' 匹配所有以 '/' 開頭的路由(注意/前不要加點./) */}
                <Route path="/" exact component={Home} />
                <Route path="/about" component={About} />
            </div>
        );
    }
}

export default RouterSwitch;

 Home.js:

import React from 'react';

class Home extends React.Component{
    render(){
        return(
            <div>主頁</div>
        );
    }
}

export default Home;

About.js:

import React from 'react';

class About extends React.Component{
    render(){
        return(
            <div>關於我們</div>
        );
    }
}

export default About;

 效果:

3.2應用場景(二)——商品主頁和詳情頁展示

最近生活拮据,想在應用中賣點東西補貼一下家用

需求:商品展示(詳情頁和主頁共享數據)

3.2.1狀態提升

前面講組件中提到過組件狀態提升,如果多個組件需要共享一個狀態,那麼就講狀態提升到使用該狀態的共同父級組件上

./BaseApp/Index.js

this.state = {
  items: [
    {
      id: 1,
      name: 'iPhone XR',
      price: 542500
    },
    {
      id: 2,
      name: 'Apple iPad Air 3',
      price: 377700
    },
    {
      id: 3,
      name: 'Macbook Pro 15.4',
      price: 1949900
    },
    {
      id: 4,
      name: 'Apple iMac',
      price: 1629900
    },
    {
      id: 5,
      name: 'Apple Magic Mouse',
      price: 72900
    },
    {
      id: 6,
      name: 'Apple Watch Series 4',
      price: 599900
    }
  ]
}

3.2.2傳遞 props

<Route exact path='/' component={Home}

如果 Route 使用的是 component 來指定組件,那麼不能使用 props

Route : render

<Route exact path='/' render={() => <Home items={this.state.items} />} />

通過 render 屬性來指定渲染函數,render 屬性值是一個函數,當路由匹配的時候指定該函數進行渲染

3.2.3渲染列表

Home.js

import React from 'react';

import {Link} from 'react-router-dom';

export default class Home extends React.Component {

    render() {
        let {items} = this.props;

        return (
            <div>
                <h2>商品列表</h2>
                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        items.map(item=>(
                            <li key={item.id}>
                                <span>
                                    <Link to={'/Item/' + item.id}>{item.name}</Link>
                                </span>
                                <span>¥ {(item.price / 100).toFixed(2)}</span>
                            </li>
                        ))
                    }
                </ul>
            </div>
        );
    }

}

params

在路由中,params 指的是 path 部分可變的部分,在這裏我們需要定義一個新的路由用來展示商品的詳情,我們希望通過 /item/1/item/2/item/…… 來訪問對應 id 的商品,那麼後面的數字部分就是可變的 - params,我們也稱這樣的路由爲:動態路由

3.2.4動態路由

爲了能給處理上面的動態路由地址的訪問,我們需要爲 Route 組件配置特殊的 path

<Route path='/item/:id' component={Item} />

3.2.4.1path-to-regexp

path-to-regexp 是一個專門用來處理 URL 的庫,它用來了一種類似正則的字符串表示法

: 表示後面的部分爲可變,: 後面的單詞爲匹配後的內容存儲的名稱,如:/item/1 ,就是 id=1

默認情況下,它爲我們提供了幾個常用的字符表示:*?+

但是我們可以通過 () 來使用正則

<Route path='/item/:id(\d+)' component={Item} />

上面表示 /item/ 後面只能是數字

3.2.4.2Route : props

當動態路由匹配以後,我們可以通過對應組件的 props 屬性來訪問當前路由匹配信息

3.2.5路由組件

如果一個組件是通過路由直接訪問的,我們稱爲:路由組件 - 視圖組件,其它的類型,根據具體功能可以稱爲:業務組件UI 組件容器組件,……

如果一個組件是路由組件,那麼組件的 props 屬性就會自動添加幾個與路由有關的幾個屬性

  • history : 對象,提供了一些用於操作路由的方法,類似原生 JavaScript 中的 history 對象。(可通過this.props.history.push('/')或this.props.go()進行跳轉)
  • location : 對象,通過它可以獲取當前路由 URL 信息,類似原生 JavaScript 中的 history.location 對象
  • match : 對象,當前路由解析後的 URL 信息

match : params

this.props.match.params.id

注意:非路由組件是沒有路由數據的

如果一個組件既要接收手動傳入的 props,又想接收路由信息

<Route path='/item/:id(\d+)' render={props => <Item {...props} items={this.state.items} />} />

商品詳情

render() {
  let items = this.props.items;
  let id = Number(this.props.match.params.id) || 0;
  let item = items.find(item => item.id === id);

  return item ? (
    <div>
      <h2>商品詳情 - {item.name}</h2>
      <dt>ID</dt>
      <dd>{item.id}</dd>
      <dt>名稱</dt>
      <dd>{item.name}</dd>
      <dt>價格</dt>
      <dd>¥ {(item.price / 100).toFixed(2)}</dd>
    </div>
  ) : <div>不存在該商品!</div>;
}

 場景二完整案例代碼:

items.js數據,router.css見上

RouterSwitch.js:因爲商品展示是和主頁及關於我們一樣,所以Route組件設置需在RouterSwitch.js實現

import React from 'react';
import { Route, Link } from 'react-router-dom';

import Home from './Home';
import About from './About';
import Item from './Item';

// 注意:所有用到的數據都需要進行導出
import items from '../data/items.js';
/**
 * 場景一:使用路由無刷新切換頁面
 */
class RouterSwitch extends React.Component {
    render() {
        return (
            <div>
                {/* <Link>路由組件: 相當於a標籤功能;to屬性相當於a標籤的href屬性;攔截了a標籤中點擊後刷新跳轉*/}
                <nav>
                    <Link to="/">首頁</Link>
                    <span> | </span>
                    <Link to="/about">關於我們</Link>
                </nav>
                <br />
                {/* Route 組件:用於設置路由信息,path屬性匹配路徑;component設置要顯示的組件;exact表示精確匹配,非 exact 模式下 '/' 匹配所有以 '/' 開頭的路由(注意/前不要加點./) */}
                {/* <Route path="/" exact component={Home} />
                <Route path="/about" component={About} /> */}

                {/* 場景二:商品展示主頁。*/}
                {/* 如果Route組件需要傳遞數據,就不能使用compoment屬性,而是需要使用render屬性 
                render 屬性值是一個函數,當路由匹配的時候指定該函數進行渲染
                */}
                <Route path="/" exact render={el => <Home items={items} />} />
                <Route path="/about" component={About} />

                {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣,所以應該在此組件中切換 */}
                {/* /item/:id(\d+)表示id後只能是數字,params是path路徑下的可變部分,如/item/1 */}
                {/* 也需要將數據items傳遞到Item頁面,這裏是路由組件,所以props屬性會自動添加幾個與路由有關的幾個屬性history:對象,location:對象,match:對象
                通過...props將props屬性中所有對象傳遞到Item組件,且傳入items*/}
                <Route path='/Item/:id(\d+)' render={(props)=><Item {...props} items={items} />} />
            </div>
        );
    }
}

export default RouterSwitch;

 Home.js:處理商品展示,和通過Link組件鏈接到Item組件

import React from 'react';
import '../css/router.css';
import { Link, Route } from 'react-router-dom';

class Home extends React.Component {
    constructor(props) {
        //商品數據通過props傳遞過來
        super(props)
    }
    render() {
        //此處得到的數據已經是數組
        let { items: { items } } = this.props;
        return (
            <div>
                {/* <div>主頁</div> */}
                {/* 場景二:商品展示主頁 */}
                <h2>商品列表</h2>
                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        // 點擊某行進入詳情頁
                        items.map(item => (
                            <li key={item.id}>
                                <span>
                                 {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣切換不同頁面,所以應該在RouterSwitch組件中使用Route組件 */}
                                    <Link to={'/Item/'+item.id}>{item.name}</Link>
                                </span>
                                <span>¥ {(item.price / 100).toFixed(2)} </span>
                            </li>
                        ))
                    }
                </ul>
            </div>
        );
    }
}

export default Home;

Item.js:獲取點擊的商品詳情展示

import React from 'react';

class Item extends React.Component{
    constructor(props){
        super(props);
    }
    render(){
        //此處可得到所有傳過來的history,match,location,items所有數據
        // let {history,match:{params:{id}},location,items} = this.props;
        let {match:{params:{id}},items:{items}} = this.props;
        //通過傳遞過來的id匹配到數據中的數據,注意從match.params中獲得的id是string,必須轉爲數字類型
        let itemInfo = items.find(item=>item.id === Number(id));
             
        return itemInfo ? (
            <div>
              <h2>商品詳情 - {itemInfo.name}</h2>
              <dt>ID</dt>
              <dd>{itemInfo.id}</dd>
              <dt>名稱</dt>
              <dd>{itemInfo.name}</dd>
              <dt>價格</dt>
              <dd>¥ {(itemInfo.price/100).toFixed(2)}</dd>
            </div>
          ) : <div>獲取商品詳情出錯</div>;
    }
}

export default Item;

 效果:

3.3應用場景(三)——價格排序

我想增加一個功能,用戶可以按照商品價格的高低進行選擇展示。

3.3.1通過 JavaScript 實現排序切換

Home.js

以非受控組件方式控制select框排序。

import React from 'react';
import '../css/router.css';
import { Link, Route } from 'react-router-dom';

class Home extends React.Component {
    constructor(props) {
        //商品數據通過props傳遞過來
        super(props);
        this.state = {
            sort : 'desc'
        };
        this.sortItems = this.sortItems.bind(this);
    }

    //商品排序(注意select需解構直value層,注意select單選的非受控組件也是通過{target:{value}}進行解構)
    sortItems({target:{value:sort}}){
        this.setState({
            sort
        });
    }

    render() {
        //此處得到的數據已經是數組
        let { items: { items } } = this.props;
        //根據sort的值將items進行排序
        items.sort((a,b)=>{
            return this.state.sort === 'desc'?a.price - b.price : b.price-a.price;
        });

        return (
            <div>
                {/* <div>主頁</div> */}
                {/* 場景二:商品展示主頁 */}
                <h2>商品列表</h2>
                排序:
                <select value={this.state.sort} onChange={this.sortItems}>
                    <option value="desc">從低到高</option>
                    <option value="asc">從高到低</option>
                </select>
                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        // 點擊某行進入詳情頁
                        items.map(item => (
                            <li key={item.id}>
                                <span>
                                 {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣切換不同頁面,所以應該在RouterSwitch組件中使用Route組件 */}
                                    <Link to={'/Item/'+item.id}>{item.name}</Link>
                                </span>
                                <span>¥ {(item.price / 100).toFixed(2)} </span>
                            </li>
                        ))
                    }
                </ul>
            </div>
        );
    }
}

export default Home;

問題:刷新頁面、分享 URL 都會丟失狀態。如本來已經從高到低排序好了,分享頁面打開後排序又恢復默認從低到高排序

3.3.2通過路由實現排序切換

3.3.2.1queryString

通常我們把 URL ? 後面的內容稱爲 queryString,在 React.js 中,我們可以通過 this.props.location.search 來獲取,它的值是字符串,格式爲:?k1=v1&k2=v2,爲了方便操作,我們把它轉成對象形式

3.3.2.2操作queryString的原生類URLSearchParams

在原生 JavaScript 中內置了一個 URLSearchParams 的類,我們通過它可以很方便的操作 queryString。URLSearchParams 的類會將內部的search變成一個對象。問題:只能解構一層對象的數據。如果要解構多層對象,需要使用第三方庫qs庫。

let {location: {search}} = this.props;
let qs = new URLSearchParams(search);
let sort = qs.get('sort');

3.3.3擴展

3.3.3.1qs 庫

https://www.npmjs.com/package/qs

3.3.3.2安裝

npm i -S qs

3.3.3.3使用

通過search獲取的對象包含?,可以使用queryString.parse(search, {ignoreQueryPrefix: true});和queryString.parse(search).substring(1);兩種方法去掉?。

import queryString from 'qs';
​
let qsTest = queryString.parse(search, {ignoreQueryPrefix: true});
let sort = qsTest.sort;

3.3.3.4通過 JavaScript 編程的方式切換路由

除了使用 <Link> 組件像 a 一樣點擊跳轉路由,我們還可以通過編程的方式(JavaScript) 來切換路由。點擊切換排序方式時直接改變URL,且直接跳轉。跳轉使用props屬性下的對象history下的push()方法,push()參數即爲跳轉後的queryString

let {history} = this.props;
<select defaultValue={sort} onChange={({target:{value}})=>{
    history.push('/?sort=' + value);
  }}>
  <option value="desc">從高到低</option>
  <option value="asc">從低到高</option>
</select>

 完整實現排序代碼:

  • 因爲刷新或分享頁面後sort狀態改變導致排序混亂,所以需要選擇排序後,同時改變URL,且直接跳轉,此時使用history對象下的push()方法。因爲此時仍然跳轉到Home組件即非路由組件,所以需要同時傳遞props;
  • 通過location對象獲取queryString。並使用原生NewSearchParams類或qs類庫處理

RouteSwitch_sort.js:

import React from 'react';
import { Route, Link } from 'react-router-dom';

import Home from './Home_sort';
import About from '../About';
import Item from '../Item';

// 注意:所有用到的數據都需要進行導出
import items from '../../data/items.js';
/**
 * 場景三:解決刷新頁面和分享鏈接後,排序的sort失效問題
 */
class RouterSwitch extends React.Component {
    render() {
        return (
            <div>
                {/* <Link>路由組件: 相當於a標籤功能;to屬性相當於a標籤的href屬性;攔截了a標籤中點擊後刷新跳轉*/}
                <nav>
                    <Link to="/">首頁</Link>
                    <span> | </span>
                    <Link to="/about">關於我們</Link>
                </nav>
                <br />
                {/* Route 組件:用於設置路由信息,path屬性匹配路徑;component設置要顯示的組件;exact表示精確匹配,非 exact 模式下 '/' 匹配所有以 '/' 開頭的路由(注意/前不要加點./) */}
                {/* <Route path="/" exact component={Home} />
                <Route path="/about" component={About} /> */}

                {/* 場景二:商品展示主頁。*/}
                {/* 如果Route組件需要傳遞數據,就不能使用compoment屬性,而是需要使用render屬性 
                render 屬性值是一個函數,當路由匹配的時候指定該函數進行渲染
                */}
                {/* <Route path="/" exact render={el => <Home items={items} />} /> */}

                {/* 場景三:排序且解決sort失效問題*/}
                {/* 跳轉到Home組件時,直接帶上sort排序,而Home組件是非路由組件,所以需要同時傳入props屬性 */}
                <Route path="/" exact render={(props) => <Home {...props} items={items} />} />

                <Route path="/about" component={About} />

                {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣,所以應該在此組件中切換 */}
                {/* /item/:id(\d+)表示id後只能是數字,params是path路徑下的可變部分,如/item/1 */}
                {/* 也需要將數據items傳遞到Item頁面,這裏是路由組件,所以props屬性會自動添加幾個與路由有關的幾個屬性history:對象,location:對象,match:對象
                通過...props將props屬性中所有對象傳遞到Item組件,且傳入items*/}
                <Route path='/Item/:id(\d+)' render={(props)=><Item {...props} items={items} />} />

                
            </div>
        );
    }
}

export default RouterSwitch;

 Home_sort.js:

import React from 'react';
import '../../css/router.css';
import { Link, Route } from 'react-router-dom';
import QS from 'qs';
/**
 * 應用場景三:排序並解決sort失效問題
 * 注意:獲取search中sort的地方和排序的地方,是在render方法中,因爲無論是首次進入首頁還是排序後跳轉到首頁都需要重新進行獲取search和排序
 */
class Home extends React.Component {
    constructor(props) {
        //商品數據通過props傳遞過來
        super(props);
    }

    render() {
        //注意需要此處就獲取queryString並進行排序
        let { items: { items }, history, location:{search} } = this.props;
        //通過原生JS類URLSearchParams的get()方法獲取sort的值,當首次進入時沒有search對象,直接設置爲asc,在select中也設置defaultValue="asc"
        // let sort = new URLSearchParams(search).get('sort') || 'asc';
        
        //原生URLSearchParams類只使用與一層對象的解構,多層對象解構時就必須使用qs類庫
        // let sort = QS.parse(search.substring(1)).sort;
        let sort = QS.parse(search,{ignoreQueryPrefix:true}).sort;
        
        //排序
        items.sort((a,b)=>{
            return sort === 'desc'? a.price-b.price : b.price-a.price;
        });

        return (
            <div>
                {/* <div>主頁</div> */}
                {/* 場景二:商品展示主頁 */}
                <h2>商品列表</h2>
                排序:
                {/* 切換時直接跳轉 */}
                <select defaultValue="asc" onChange={({ target: { value } }) => {
                    //注意此處不能使用return
                    history.push('/?sort=' + value);
                }}>
                    <option value="desc">從低到高</option>
                    <option value="asc">從高到低</option>
                </select>
                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        // 點擊某行進入詳情頁
                        items.map(item => (
                            <li key={item.id}>
                                <span>
                                    {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣切換不同頁面,所以應該在RouterSwitch組件中使用Route組件 */}
                                    <Link to={'/item/' + item.id}>{item.name}</Link>
                                </span>
                                <span>¥ {(item.price / 100).toFixed(2)} </span>
                            </li>
                        ))
                    }
                </ul>
            </div>
        );
    }
}

export default Home;

3.4應用場景(四)——頁面頂部導航高亮

現在,我想給頁面頂部的導航加上高亮效果,用來標識當前頁面。這個時候,我們就可以使用 react-router-dom 提供的 NavLink 組件來實現。

3.4.1NavLink 組件

NavLinkLink 類似,但是它提供了兩個特殊屬性用來處理頁面導航

Failed prop type: Invalid prop `activeStyle` activeStyle後的style樣式寫法有錯。

3.4.1.1activeStyle

噹噹前 URLNavLink 中的 to 匹配的時候,激活 activeStyle 中的樣式

3.4.1.2activeClassName

activeStyle 類似,但是激活的是 className

3.4.1.3isActive

默認情況下,匹配的是 URLto 的設置,通過 isActive 可以自定義激活邏輯isActive 是一個函數,返回布爾值

index.js

<NavLink to="/" activeStyle={{color:'red'}} isActive={(match, location) => {
    return match || location.pathname.startsWith('/item')
}} exact>Home</NavLink>
<span> | </span>
<NavLink to="/about" activeStyle={{color:'red'}} exact>About</NavLink>

3.3.1.4exact

精確匹配

應用場景(四)——高亮導航完整代碼實現:

import React from 'react';
import { Route, Link, NavLink } from 'react-router-dom';

import Home from '../sort/Home_sort';
import About from '../About';
import Item from '../Item';
import items from '../../data/items.js';
import '../../css/router.css';
/**
 * 場景四:導航高亮
 * <NavLink>組件
 */
class RouterSwitch extends React.Component {
    render() {
        return (
            <div>
                {/* <Link>路由組件: 相當於a標籤功能;to屬性相當於a標籤的href屬性;攔截了a標籤中點擊後刷新跳轉*/}
                <nav>
                    {/* Failed prop type: Invalid prop `activeStyle` activeStyle後的style樣式寫法有錯
                    isActive是函數,返回值爲布爾值,爲TRUE則設置高亮樣式,FALSE則不設置
                    */}
                    <NavLink to="/" exact activeStyle={{'color':'red'}} isActive={(match,location)=>{
                        //注意RouterSwitch組件不是路由組件,不能直接獲取match,所以需要傳入props
                        return match || location.pathname.startsWith("/item");
                    }}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/about" exact activeClassName={"homeHighLight"}>關於我們</NavLink>

                </nav>
                <br />
                {/* Route 組件:用於設置路由信息,path屬性匹配路徑;component設置要顯示的組件;exact表示精確匹配,非 exact 模式下 '/' 匹配所有以 '/' 開頭的路由(注意/前不要加點./) */}
                {/* <Route path="/" exact component={Home} />
                <Route path="/about" component={About} /> */}

                {/* 場景二:商品展示主頁。*/}
                {/* 如果Route組件需要傳遞數據,就不能使用compoment屬性,而是需要使用render屬性 
                render 屬性值是一個函數,當路由匹配的時候指定該函數進行渲染
                */}
                {/* <Route path="/" exact render={el => <Home items={items} />} /> */}

                {/* 場景三:排序且解決sort失效問題*/}
                {/* 跳轉到Home組件時,直接帶上sort排序,而Home組件是非路由組件,所以需要同時傳入props屬性 */}
                <Route path="/" exact render={(props) => <Home {...props} items={items} />} />

                <Route path="/about" component={About} />

                {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣,所以應該在此組件中切換 */}
                {/* /item/:id(\d+)表示id後只能是數字,params是path路徑下的可變部分,如/item/1 */}
                {/* 也需要將數據items傳遞到Item頁面,這裏是路由組件,所以props屬性會自動添加幾個與路由有關的幾個屬性history:對象,location:對象,match:對象
                通過...props將props屬性中所有對象傳遞到Item組件,且傳入items*/}
                <Route path='/item/:id(\d+)' render={(props) => <Item {...props} items={items} />} />

            </div>
        );
    }
}

export default RouterSwitch;

3.5應用場景(五)——404頁面

當用戶訪問不存在的路由,我們需要提供一個反饋頁面,也就是 404

index.js

<Route exact path='/' render={props => <Home {...props} items={this.state.items} />} />
<Route path='/about' component={About} />
<Route path='/item/:id(\d+)' render={props => <Item {...props} items={this.state.items} />} />
{/*NotFound*/}
<Route component={NotFound} />

NotFound.js

import React from 'react';
​
export default function NotFound() {
    return (
        <div>
            Not Found
        </div>
    );
}

默認情況下,React.js 會渲染所有與之匹配的路由組件,上面的案例中,當我們訪問任意路由(如:/about),也會匹配 NotFound 路由組件渲染。即路由組件是由穿透性的,所以需要使用Switch組件。

3.5.1Switch 組件

該組件只會渲染首個被匹配的組件,所以把優先級高的組件寫在最前面。

<Switch>
    <Route exact path='/' render={props => <Home {...props} items={this.state.items} />} />
    <Route path='/about' component={About} />
    <Route path='/item/:id(\d+)' render={props => <Item {...props} items={this.state.items} />} />
    {/*NotFound*/}
    <Route component={NotFound} />
</Switch>

3.6應用場景(六)——購物車

現在,我們要給用戶增加一個購物車的功能。未登錄用戶是不能訪問購物車的,所以,我們需要在訪問購物車的時候加入用戶權限驗證(鑑權),如果沒有登錄則跳轉到登錄。用戶信息的存儲通過state進行模擬。

index.js

// 添加一個連接
<NavLink to="/cart" activeStyle={{color:'red'}} exact>Cart</NavLink>

Cart.js

// 購物車組件
import React from 'react';

export default class Cart extends React.Component {

    render() {
        return(
            <div>
                購物車
            </div>
        );
    }

}

我們還要配一個用戶系統,比如:註冊登錄啥的

index.js

// 模擬一個添加登錄用戶狀態的 state
this.state = {
  	users: [
      {
        id: 1,
        username: 'baoge',
        password: '123'
      },
      {
        id: 2,
        username: 'MT',
        password: '123'
      },
      {
        id: 3,
        username: 'dahai',
        password: '123'
      },
      {
        id: 4,
        username: 'zMouse',
        password: '123'
      }
    ],
    userInfo: {
      	id: 0,
      	username: ''
    },
  	...
}
  
// 添加一個登錄鏈接
{
  	this.state.userInfo.id > 0 ? (
  			<span>{this.state.userInfo.username}</span>
  	) : (
    		<NavLink to='/login' activeStyle={{color:'red'}} exact>Login</NavLink>
  	)
}

3.6.1Redirect 組件

未登錄用戶是不能訪問購物車的,所以,我們需要在訪問購物車的時候加入用戶權限驗證(鑑權),如果沒有登錄則跳轉到登錄

index.js

<Route path='/cart' render={props => {
    if (this.state.userInfo.uid > 0) {
      		return <Cart />;
    } else {
      		// return <Login />;
      		return <Redirect to='/login' />;
    }
}}  />

to

設置跳轉的 URL

登錄代碼

// index.js
login({username, password}) {
  return new Promise( (resolve, reject) => {
    if (!username || !password) {
      reject('請輸入用戶名和密碼');
    }

    let user = this.state.users.find(user => user.username === username && user.password === password);
    if ( !user ) {
      reject('用戶不存在或者密碼錯誤');
    }

    this.setState({
      userInfo: {
        id: user.id,
        username: user.username
      }
    });

    resolve('登錄成功');
  } );
}
...
<Route path='/login' component={props => {
    return <Login {...props} onLogin={this.login.bind(this)} />;
}} />
// login.js
import React from 'react';

export default class Login extends React.Component {

    constructor(...props) {
        super(...props);

        this.login = this.login.bind(this);

        this.usernameRef = React.createRef();
        this.passwordRef = React.createRef();
    }

    login() {
        let {onLogin, history: {push}} = this.props;

        if (typeof onLogin === 'function') {
            onLogin({
                username: this.usernameRef.current.value,
                password: this.passwordRef.current.value
            }).then(msg=>{
                alert(msg);
                push('/');
            }).catch(e=>alert(e));
        } 
    }

    render() {
        return(
            <div>
                <p>
                    用戶名:<input type="text" ref={this.usernameRef} />
                </p>
                <p>
                    密碼:<input type="password" ref={this.passwordRef} />
                </p>
                <p>
                    <button onClick={this.login}>登錄</button>
                </p>
            </div>
        );
    }

}

 場景六——購物車——用戶登錄鑑權認證完整代碼實現:

分析:

  • 點擊購物車時,通過路由判斷是否登錄,登錄則顯示購物車頁面,未登錄顯示登錄頁面;
  • 將登錄信息通過state狀態保存,並通過其判斷是否登錄;
  • 由於登錄頁面進行登錄後,需要將登錄信息保存到state中,並且在路由時需要使用,所以登錄頁面的login方法需要調用路由頁面登錄方法去真正實現登錄邏輯;
  • 以上操作會影響到home頁面的props對象,所以需要通過{props}將對象傳遞到下一層

RouterSwitch_cart.js:

import React from 'react';
import { Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';

import Home from '../sort/Home_sort';
import About from '../About';
import Item from '../Item';
import items from '../../data/items.js';
import '../../css/router.css';

// 場景五:404錯誤頁面
// import NotFound from './NotFound';

// 場景六:購物車——用戶登錄鑑權認證
import Cart from '../cart/Cart';
import Login from '../cart/Login';
import usersData from '../../data/user';

/**
 * 場景六:購物車——用戶登錄鑑權認證
 */
class RouterSwitch extends React.Component {
    constructor(props){
        super(props);
        let {userInfo} = usersData;
        //需要通過state保存和更新登錄用戶信息
        this.state = {
            userInfo
        }
    }
    login({username, password}) {
        return new Promise( (resolve, reject) => {
          if (!username || !password) {
            reject('請輸入用戶名和密碼');
          }
          let {users} = usersData;
          
          let user = users.find(user => user.username === username && user.password === password);
          if ( !user ) {
            reject('用戶不存在或者密碼錯誤');
          }
      
          this.setState({
            userInfo: {
              id: user.id,
              username: user.username
            }
          });
      
          resolve('登錄成功');
        } );
      }

    render() {
        return (
            <div>
                {/* <Link>路由組件: 相當於a標籤功能;to屬性相當於a標籤的href屬性;攔截了a標籤中點擊後刷新跳轉*/}
                <nav>
                    <NavLink to="/" exact activeStyle={{ 'color': 'red' }} isActive={(match, location) => {
                        return match || location.pathname.startsWith("/item");
                    }}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/about" exact activeClassName={"homeHighLight"}>關於我們</NavLink>
                    <span> | </span>
                     {/* 場景六:購物車——用戶鑑權認證*/}
                     <NavLink to="/cart" exact activeClassName={"homeHighLight"}>購物車</NavLink>

                </nav>
                <br />
                {/* Route 組件:用於設置路由信息,path屬性匹配路徑;component設置要顯示的組件;exact表示精確匹配,非 exact 模式下 '/' 匹配所有以 '/' 開頭的路由(注意/前不要加點./) */}
                {/* <Route path="/" exact component={Home} />
                <Route path="/about" component={About} /> */}

                {/* 場景二:商品展示主頁。*/}
                {/* 如果Route組件需要傳遞數據,就不能使用compoment屬性,而是需要使用render屬性 
                render 屬性值是一個函數,當路由匹配的時候指定該函數進行渲染
                */}
                {/* <Route path="/" exact render={el => <Home items={items} />} /> */}

                {/* 場景三:排序且解決sort失效問題*/}
                {/* 跳轉到Home組件時,直接帶上sort排序,而Home組件是非路由組件,所以需要同時傳入props屬性 */}
                <Switch>
                    {/* 場景六時,注意Home組件本身不是路由組件,需要傳遞props對象 */}
                    <Route path="/" exact render={(props) => <Home {...props} items={items} />} />

                    <Route path="/about" component={About} />

                    {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣,所以應該在此組件中切換 */}
                    {/* /item/:id(\d+)表示id後只能是數字,params是path路徑下的可變部分,如/item/1 */}
                    {/* 也需要將數據items傳遞到Item頁面,這裏是路由組件,所以props屬性會自動添加幾個與路由有關的幾個屬性history:對象,location:對象,match:對象
                通過...props將props屬性中所有對象傳遞到Item組件,且傳入items*/}
                    <Route path='/item/:id(\d+)' render={(props) => <Item {...props} items={items} />} />

                    {/* 場景五:404錯誤頁面*/}
                    {/* 如果直接加入Route會每個頁面都有404錯誤頁面 */}
                    {/* <Route component={NotFound} /> */}

                     {/* 場景六:購物車——用戶鑑權認證
                        判斷如果userInfo中id不大於0則沒有登錄,需要重定向到登錄頁面
                     */}
                     <Route path="/login" render={(props)=><Login {...props} onLogin={this.login.bind(this)}/>}/>
                     <Route path="/cart" render={()=>{
                        //  注意此處的userInfo需要從state中取,登錄後會保存進state中
                         if(this.state.userInfo.id>0){
                             console.log("購物車");
                             
                            return <Cart />
                         }else{
                             console.log("未登錄");
                             
                            return <Redirect to='/login'/>
                         }
                     }} />

                </Switch>

            </div>
        );
    }
}

export default RouterSwitch;

 Login.js:

import React from 'react';

/**
 * 用戶登錄:通過ref回調方式,獲取並設置非受控組件的值
 */
class Login extends React.Component {
    constructor(props) {
        super(props);
        this.login = this.login.bind(this);

        this.usernameRef = React.createRef();
        this.passwordRef = React.createRef();
    }

    login() {
        let {onLogin, history: {push}} = this.props;

        if (typeof onLogin === 'function') {
            onLogin({
                username: this.usernameRef.current.value,
                password: this.passwordRef.current.value
            }).then(msg=>{
                alert(msg);
                push('/');
            }).catch(e=>alert(e));
        } 
    }
    render() {
        return (
            <div>
                用戶名:<input ref={this.usernameRef} type="text" name="username" /><br />
                    密碼:<input ref={this.passwordRef} type="password" name="password" /><br />
                <button onClick={this.login}>登錄</button>
            </div>
        );
    }
}
export default Login;

 Cart.js購物車頁面和users.js數據代碼見上。

3.7應用場景(七)——分頁組件

我突然發現賣東西比做碼農要賺錢(主要是能生髮),所以加大產品投入量,期待有一天,我的頭髮要寶哥多,然後推薦給莫莫

隨着產品的增多,那麼多的數據就不可能一次性全部展示出來,所以我們爲商品的展示添加一個分頁功能。

  • 分頁組件:某個組件或頁面需要用到分頁功能時就引入分頁組件,然後傳遞分頁參數。
  • 且該需要分頁的頁面組件的path需要只能匹配 / 或/page(頁碼數字類型),否則不能進行路由。如<Route path="/page(\d)*" render={...return (<Home .../>)} />。
  • 跳轉到某頁,使用onKeyDown()後調用push()進行跳轉,但分頁組件不是路由組件,不能直接將props屬性及裏面的各對象直接向下傳遞。雖然我們可以通過傳參的方式傳入,但是如果結構複雜,這樣做會特別的繁瑣。幸好,我們可以通過 withRouter 方法來注入路由對象
// Pagination.js
import React from 'react';
import PropTypes from 'prop-types';

import {withRouter, Link} from 'react-router-dom';

class Pagination extends React.Component {

    static defaultProps = {
        pages: 1,
        page: 1
    }

    static propTypes = {
        pages: PropTypes.number,
        page: PropTypes.number
    }

    render() {
        //pages總共多少頁,page當前頁碼,push跳轉方法
        let {pages, page, history: {push}} = this.props;
        // console.log(this.props);

        return (
            <div className="pagination">
                {
                    (new Array(pages)).fill('').map((v, i) => {
                        return (
                            <Link 
                                key={++i}
                                className={i === page ? 'active' : ''}
                                to={'/'+i}
                            >
                                {i}
                            </Link>
                        );
                    })
                }
                前往
                <input type="text" className="goto" onKeyDown={({target:{value}})=>{
                    if (value !== '') {
                        push('/' + value);
                    }
                }} />
                頁
            </div>
        );
    }

}

export default withRouter(Pagination);

3.7.1withRouter()方法

如果一個組件不是路由綁定組件,那麼該組件的 props 中是沒有路由相關對象的,雖然我們可以通過傳參的方式傳入,但是如果結構複雜,這樣做會特別的繁瑣。幸好,我們可以通過 withRouter 方法來注入路由對象。

場景七:分頁完整代碼實現

RouterSwitch_pagenation.js:注意只有在Route路由時添加page參數,Link時不需要。<Route path="/:page(\d*)" exact render={(props) => <Home {...props} items={items}/>} />

import React from 'react';
import { Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';

// import Home from '../sort/Home_sort';
//場景七:
import Home from '../pagenation/Home_pagenation';
import About from '../About';
import Item from '../Item';
import items from '../../data/items.js';
import '../../css/router.css';

// 場景五:404錯誤頁面
// import NotFound from './NotFound';

// 場景六:購物車——用戶登錄鑑權認證
// 場景七:分頁
import Cart from '../cart/Cart';
import Login from '../cart/Login';
import usersData from '../../data/user';

/**
 * 場景六:購物車——用戶登錄鑑權認證
 */
class RouterSwitch extends React.Component {
    constructor(props){
        super(props);
        let {userInfo} = usersData;
        //需要通過state保存和更新登錄用戶信息
        this.state = {
            userInfo
        }
    }
    login({username, password}) {
        return new Promise( (resolve, reject) => {
          if (!username || !password) {
            reject('請輸入用戶名和密碼');
          }
          let {users} = usersData;
          
          let user = users.find(user => user.username === username && user.password === password);
          if ( !user ) {
            reject('用戶不存在或者密碼錯誤');
          }
      
          this.setState({
            userInfo: {
              id: user.id,
              username: user.username
            }
          });
      
          resolve('登錄成功');
        } );
      }

    render() {
        return (
            <div>
                {/* <Link>路由組件: 相當於a標籤功能;to屬性相當於a標籤的href屬性;攔截了a標籤中點擊後刷新跳轉*/}
                <nav>
                    <NavLink to="/" exact activeStyle={{ 'color': 'red' }} isActive={(match, location) => {
                        return match || location.pathname.startsWith("/item");
                    }}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/about" exact activeClassName={"homeHighLight"}>關於我們</NavLink>
                    <span> | </span>
                     {/* 場景六:購物車——用戶鑑權認證*/}
                     <NavLink to="/cart" exact activeClassName={"homeHighLight"}>購物車</NavLink>

                </nav>
                <br />
                {/* Route 組件:用於設置路由信息,path屬性匹配路徑;component設置要顯示的組件;exact表示精確匹配,非 exact 模式下 '/' 匹配所有以 '/' 開頭的路由(注意/前不要加點./) */}
                {/* <Route path="/" exact component={Home} />
                <Route path="/about" component={About} /> */}

                {/* 場景二:商品展示主頁。*/}
                {/* 如果Route組件需要傳遞數據,就不能使用compoment屬性,而是需要使用render屬性 
                render 屬性值是一個函數,當路由匹配的時候指定該函數進行渲染
                */}
                {/* <Route path="/" exact render={el => <Home items={items} />} /> */}

                {/* 場景三:排序且解決sort失效問題*/}
                {/* 跳轉到Home組件時,直接帶上sort排序,而Home組件是非路由組件,所以需要同時傳入props屬性 */}
                <Switch>
                    {/* 場景六時,注意Home組件本身不是路由組件,需要傳遞props對象 
                      場景七時,傳入page參數和正則限定
                    */}
                    <Route path="/:page(\d*)" exact render={(props) => <Home {...props} items={items}/>} />

                    <Route path="/about" component={About} />

                    {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣,所以應該在此組件中切換 */}
                    {/* /item/:id(\d+)表示id後只能是數字,params是path路徑下的可變部分,如/item/1 */}
                    {/* 也需要將數據items傳遞到Item頁面,這裏是路由組件,所以props屬性會自動添加幾個與路由有關的幾個屬性history:對象,location:對象,match:對象
                通過...props將props屬性中所有對象傳遞到Item組件,且傳入items*/}
                    <Route path='/item/:id(\d+)' render={(props) => <Item {...props} items={items} />} />

                    {/* 場景五:404錯誤頁面*/}
                    {/* 如果直接加入Route會每個頁面都有404錯誤頁面 */}
                    {/* <Route component={NotFound} /> */}

                     {/* 場景六:購物車——用戶鑑權認證
                        判斷如果userInfo中id不大於0則沒有登錄,需要重定向到登錄頁面
                     */}
                     <Route path="/login" render={(props)=><Login {...props} onLogin={this.login.bind(this)}/>}/>
                     <Route path="/cart" render={()=>{
                        //  注意此處的userInfo需要從state中取,登錄後會保存進state中
                         if(this.state.userInfo.id>0){
                             console.log("購物車");
                             
                            return <Cart />
                         }else{
                             console.log("未登錄");
                             
                            return <Redirect to='/login'/>
                         }
                     }} />

                </Switch>

            </div>
        );
    }
}

export default RouterSwitch;

Home_pagenation.js:分頁使用slice方法實現

import React from 'react';
import '../../css/router.css';
import { Link, Route } from 'react-router-dom';

import Pagenation from './Pagenation';

class Home extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        //此處得到的數據已經是數組
        let { items: { items },match:{params:{page}} } = this.props;
        console.log(page);
        
        //每頁條數
        let num = 3;
        let pageNum = Number(page);
        let itemData = items.slice((pageNum-1)*num,page*num);
        return (
            <div>
                {/* <div>主頁</div> */}
                {/* 場景二:商品展示主頁 */}
                <h2>商品列表</h2>
                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        // 點擊某行進入詳情頁
                        itemData.map(item => (
                            <li key={item.id}>
                                <span>
                                 {/* 注意Item的Route組件因爲是和主頁及關於我們頁面一樣切換不同頁面,所以應該在RouterSwitch組件中使用Route組件 */}
                                    <Link to={'/item/'+item.id}>{item.name}</Link>
                                </span>
                                <span>¥ {(item.price / 100).toFixed(2)} </span>
                            </li>
                        ))
                    }
                </ul>
                {/*場景七: 引入分頁組件 */}
                <Pagenation pages={items.length/num} page={Number(page)}/>
            </div>
        );
    }
}

export default Home;

Pagenation.js:注意withRouter()的使用

import React from 'react';
import { Link, withRouter } from 'react-router-dom';

import { number } from 'prop-types';
/**
 * 場景七:分頁組件
 */
class Pagenation extends React.Component {
    constructor(props) {
        super(props);
    }

    //設置總頁數和頁碼的初始值和類型驗證
    static defaultProps = {
        pages: 1,
        page: 1
    };
    static propTypes = {
        pages: number.isRequired,
        page: number.isRequired
    };

    render() {
        let { pages, page, history: { push } } = this.props;
        return (
            <div className="pagination">
                {
                    (new Array(pages)).fill('').map((v, i) => {
                        return (
                            <Link
                                key={++i}
                                className={i === page ? 'active' : ''}
                                to={'/' + i}
                            >
                                {i}
                            </Link>
                        );
                    })
                }
                前往
                <input type="text" className="goto" onKeyDown={({ target: { value } }) => {
                    if (value !== '') {
                        push('/' + value);
                    }
                }} />
                頁
            </div>
        );
    }
}
 //通過使用withRouter就不需要一層層傳遞props屬性,可直接在此獲取,否則history等信息需要從Home組件傳過來
export default withRouter(Pagenation);

效果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章