React 0基礎學習路線(6)— 圖解深度詳述React路由及其詳細案例(附詳細案例代碼解析過程)

文章目錄

1. 重點提煉

React 路由

  • React-Router
    • Native
    • Web : react-router-dom
  • BrowserRouter / HashRouter:路由根組件,提供路由基礎服務
  • Route:路由組件,處理 url 與 組件的 映射(綁定)
  • Link:動態生成 a 標籤,並提供導航服務

2. React.js - 路由react-router-dom

2.1 路由

實際上這個路由,和我們平時使用的路由器是一樣的,沒太大差異,其目的就是內容分發(分流)。其實就是將一整塊內容,切分成一個個部分,根據某種規則進行分塊的訪問。比如現在有很多電腦,你想把它們納入到一起進行管理,這個時候就需要購買一個路由器了。它核心作用其實就是處理我們每次發送的請求以及數據。就相當於我們現在擁有一個龐大的資源,那麼資源怎麼去管理呢?不可能每次請求,都把這麼龐大的資源都塞給接受者,發送人和請求人都會累奔潰。一般把資源按照不同的類別和需求進行分門別類,把它組織好,分好,這個時候得需要某種方式得到數據。

一般情況下都是用url來獲得數據的,通過它與後端對應的服務與資源做對應,即通過不同的url即可得到後端不同的數據,其實是做內容分發用的,所以實際前端路由概念是很類似的。

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

如a、b兩個頁面都是一樣的,有99%是一樣的,就有一些文字不同。如果從a頁面切換到b頁面,也等同於重新發了一個請求,去請求b頁面的資源,瀏覽器就會把整個頁面進行重新渲染,這樣對資源是極其地浪費。

爲了解決這樣的問題,用js的方式去拉去數據,而不是一整個頁面,而是變化地部分。如第一次請求是一個首頁,瀏覽器請求整個頁面。但第二次比如跳轉詳情頁面,就沒必要去請求一個完整的頁面過來了,這時候我們是通過ajax往後端發送請求,後端傳遞的數據僅僅是頁面當中不同的部分的數據,而不是整個新頁面。當前端頁面拿到這個數據以後,就動態地通過dom的一系列操作,把頁面需要變化的部分給替換掉,不變地部分保留下來。這樣的操作其實就不是通過瀏覽器本身發送的請求,所以得到的內容是不會被瀏覽器重新渲染的。它會通過js,利用操作dom的方式進行局部更新。即通過這樣地方式,節約很多性能,同時也不會改變頁面的狀態。

如有一個複雜的頁面,頁面當中有一個表單,正則填寫的時候,側面有一個新聞。寫得過程中,想一邊看新聞,需要更新一下新聞,一點擊,只會通過js把新聞的部分替換掉,我們的表單是不會動的,這個時候並不是渲染整個頁面,它能夠更好的保留用戶在頁面當中的操作狀態,即用戶體驗是非常好的。我們通常稱這種模式爲SPA(單頁面應用)。

2.1.1 SPA

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

優點:

  • 有更好的用戶體驗(減少請求和渲染和頁面跳轉產生的等待與空白),頁面切換快(不會導致整個頁面重新渲染)
  • 重前端,數據和頁面內容由異步請求(AJAX)+ DOM 操作來完成,前端處理更多的業務邏輯

缺點:(會有很多技術去解決這些問題)

  • 首屏處理慢(因爲把很多數據和邏輯都放在前端了,頁面相當於反應速度會慢一些)

  • 不利於 SEO(搜索引擎的優化)

2.1.2 SPA 的頁面切換機制

SPA 的頁面切換機制主要有兩種方式。

SPA雖然是單頁面的,但是如果全部通過Ajaxdom渲染去完成的話,如我們現在去請求一個首頁,用Ajax方式請求內容,會有一個問題。如果這個時候想單看列表,不想看首頁這麼多的內容呢?這個時候你就會發現我們的操作,不可跳轉,即必須按照某種方式:第一步、第二步、…、一直點,點到你看到的列表爲止。它是不能作爲單獨的方式給你呈現的,因爲所有的後續操作都是通過這個入口完成的,必須先進入口,通過入口一步步操作才能夠看到我們想看的東西。這樣的話,會非常繁瑣和麻煩。這個時候就想到一個辦法,僞造url定位到不同的操作當中。不需要點擊n多步,就可以訪問到n多步操作完成後的頁面。但是我們也不希望這個url產生真正的請求,這個時候我們採取的方式就是:

前端路由主要的模式(通過這兩種方式更改url,但更改url的同時又不會發送請求,然後再去監聽它的事件,把頁面渲染的邏輯放在回調函數中,當js發現url變化了,則會動態地根據url的變化,去渲染我們想要看到的頁面內容。):

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

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

2.1.3 後端路由與前端路由

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

2.1.3.1 後端路由

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

  • 需要發送 HTTP 請求
  • 業務邏輯由後端處理,返回處理後的結果給前端(瀏覽器)

2.1.3.2 前端路由

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

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

目前前端路由主要的模式:

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

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

優點

兼容性好

缺點

URL 不美觀,SEO 不友好

2.1.3.2.2 HTML5 History API

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

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

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

2.1.4 React.js 中的路由

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

2.2 React Router

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

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

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

  • web
  • native

2.2.1 基於 WebReact Router

基於 webReact Router 爲:react-router-dom

2.2.1.1 安裝

npm i -S react-router-dom

2.2.1.2 概覽

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

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

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

2.3 基礎

2.3.1 應用場景(一)

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

/                :    首頁
/about    :    關於我們

用戶一進來的是首頁,在裏面的鏈接或按鈕,點擊之後跳到“關於我們”的頁面。

現在不用傳統的方式寫兩個html頁面,發送兩次請求,而是用React搭建。React渲染過後會形成一個頁面,這個頁面裏看到的內容其實就是主件,實際上就是把一個個頁面轉換成一個個的組件。然後再通過url的形式,它是基於htmlhistoryapi,或者通過urlhash兩種模式,當url發生變化的時候,來給用戶展示不同的組件。其實就是頁面的某個部分會根據不同的url而變化,來展示不同的組件內容,這其實就是前端路由的概念。

2.3.1.1 Router 組件

頁面 = 組件
路由:根據不同的 url 的變化,在我們的應用中指定的位置渲染不同的組件,從而顯示出來不同的內容

組件劃分類型(注意它們不是互相獨立的,是相互可能存在關係的,這裏只是做功能上的劃分):

  • 頁面組件(視圖組件),一個頁面組件一般對應的就是完整的頁面(完整:一個url所表示的頁面)(如:登頁和註冊頁等)
  • 功能組件(具有一定功能的,但一般情況下又不是一個完整的頁面的組件,輪播圖,對話框等,功能組件就是面組件的一部分)
    • 帶視圖的功能組件(輪播圖)
    • 不帶視圖的功能組件,即業務型的功能組件(數據過濾數據請求 - 作爲容器錯誤捕獲(把錯誤發給指定郵箱)
  • 容器組件

項目整個結構也需要分類:

components(功能型組件、容器型組件)

views(視圖型組件)

2.3.1.1.1 exmaple01

應用場景(一)

2.3.1.1.1.1 exmaple01-1

框子實現。

app.js

import React from 'react';
 
import Home from './views/Home';
import About from './views/About';
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
    }
 
    render() {
        return (
            <div className="App">
                <Home/>
                <About/>
            </div>
        )
    }
 
}
 
export default App;

src/views/Home.js

import React from 'react';
 
export default class Home extends React.Component {
 
    render() {
        return (
            <div>
                <h2>商品列表</h2>
            </div>
        );
    }
 
}

src/views/About.js

import React from 'react';
 
export default class About extends React.Component {
 
    render() {
        return(
            <div>
                關於我們
            </div>
        );
    }
 
}

image-20200605231442716

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.01-1
Branch:branch4

commit description:v3.01-1-example01-1(應用場景(一)- 框子實現)

tag:v3.01-1

2.3.1.1.1.2 example01-2
console.log(window.location);

hash其實就是url後面#的內容

image-20200606102911256

image-20200606103120690

通過hash設置默認首頁及url

app.js

 
 
import React from 'react';
 
 
import Home from './views/Home';
import About from './views/About';
 
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
    }
 
    render() {
        // console.log(window.location);
 
        let hash = window.location.hash || '#home';
 
        console.log(hash);
 
        return (
            <div className="App">
                <Home/>
                <About/>
            </div>
        )
    }
 
}
 
export default App;

 
 
import React from 'react';
 
 
import Home from './views/Home';
import About from './views/About';
 
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
    }
 
    render() {
        // console.log(window.location);
 
        let hash = window.location.hash || '#home';
 
        console.log(hash);
 
        return (
            <div className="App">
                {/*<Home/>*/}
                {/*<About/>*/}
 
                {
                    hash === '#home' && <Home />
                }
 
                {
                    hash === '#about' && <About />
                }
            </div>
        )
    }
 
}
 
export default App;

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.01-2
Branch:branch4

commit description:v3.01-2-example01-2(應用場景(一)- 通過hash設置默認首頁及url)

tag:v3.01-2

這裏的原理雖然不難,但是要是實現用戶點擊,跳轉頁面。需要監聽hashchange的變化,考慮hashurl的規則,實際上整個頁面會更加複雜,實際需求是複雜的。實際上React-router的內部就是運用這些原理的,只是封裝好之後,提供更好的方式供使用,其內部封裝了很多組件。

如果我們希望頁面中某個部分的內容需要根據 URL 來動態顯示,需要用到 Router 組件 ,該組件是一個容器組件,只需要用它包裹 URL 對應的根組件即可

react-router-dom 爲我們提供了幾個基於不同模式的 router 子組件(功能性組件且容器組件)

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

基於 HTML5 History API 的路由組件

2.3.1.1.3 HashRouter 組件

基於 URL Hash 的路由組件

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';

2.3.1.2 Route 組件

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

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

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

2.3.1.2.1 exact

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

2.3.1.3 Link 組件

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

2.3.1.3.1 to 屬性

to 屬性類似 a 標籤中的 href

2.3.1.4 example02

場景(一)應用。

2.3.1.4.1 example02-1

HashRouter組件:

  • HashRouter 包含的組件才能響應路由

  • BrowserRouter: 基於 HTML5 History API

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
// import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {HashRouter} from 'react-router-dom';
 
ReactDOM.render(
    <HashRouter>
        <App />
    </HashRouter>,
    document.getElementById('root')
);
 
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Route配置url與組件的映射關係:什麼url與什麼組件現在頁面的什麼位置。所在的位置就是滿足要求以後組件渲染對應的位置。

path對應urlcomponent對應組件

src/App.js

import React from 'react';
 
import {Route} from 'react-router-dom';
 
import Home from './views/Home';
import About from './views/About';
 
class App extends React.Component {
 
    constructor(props) {
        super(props);
    }
 
    render() {
 
        return (
            <div className="App">
                <h1>React路由</h1>
                <Route path="/" component={Home}/>
            </div>
        )
    }
 
}
 
export default App;
image-20200606133418783
                    <h1>React路由</h1>
                    <hr/>
                    <Route path="/" component={Home}/>
                    <Route path="/about" component={About}/>

發現首頁也顯示進去了,因爲此時路由url既滿足首頁又滿足about,所以需要我們精確匹配。

image-20200606133723623

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.02-1
Branch:branch4

commit description:v3.02-1-example02-1(應用場景(一)- React路由匹配)

tag:v3.02-1

2.3.1.4.2 example02-2

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

                    <h1>React路由</h1>
                    <hr/>
                    {/*<Route path="/" component={Home}/>*/}
                    <Route path="/" component={Home} exact />
                    <Route path="/about" component={About}/>
image-20200606134106681

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.02-2
Branch:branch4

commit description:v3.02-2-example02-2(應用場景(一)- React路由精確匹配)

tag:v3.02-2

2.3.1.4.3 example02-3

用戶在做頁面跳轉時,不可能自己手動輸入url,所以以上是不可能得以應用的。通常我們使用UI的方式,關聯到頁面的元素,供用戶點擊跳轉。

                    <h1>React路由</h1>
                    <hr/>
                    <Route path="/" component={Home} exact />
                    <Route path="/about" component={About}/>
 
                    <nav>
                        <a href="/">首頁</a>
                        <span> | </span>
                        <a href="/about">關於我們</a>
                    </nav>

這樣是錯誤的,只會刷新頁面並毫無反應!必須帶#

                    <h1>React路由</h1>
                    <hr/>
                    <Route path="/" component={Home} exact />
                    <Route path="/about" component={About}/>                  
                    <nav>
                        <a href="#/">首頁</a>
                        <span> | </span>
                        <a href="#/about">關於我們</a>
                    </nav>

有人可能會問,爲啥可以把對應的組件顯示出來呢?

因爲react-router-dom會監聽hash的變化,我們只需要觸發hash的變化就行了,這個庫的內部會觸發hashchange事件。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.02-3
Branch:branch4

commit description:v3.02-3-example02-3(應用場景(一)- React路由精確匹配,UI的方式控制)

tag:v3.02-3

2.3.1.4.4 example02-4

但以上這種形式並不是太好(url也很醜),以上是hash模式,接下來演示historyApi的模式。

BrowserRouter: 基於HTML5 History API

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {HashRouter, BrowserRouter} from 'react-router-dom';

ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>,
    document.getElementById('root')
);
 
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

它是基於html5的,/更加美觀。

image-20200606151551880
                    <nav>
                        <a href="/">首頁</a>
                        <span> | </span>
                        <a href="/about">關於我們</a>
                    </nav>

但我們會發現一個問題,就是它是刷新了我們整個頁面的!刷新完畢後,再根據url再顯示主件。

如果有個input,就坑了!

                    <h1>React路由</h1>
                    <hr/> 
                    <nav>
                        <a href="/">首頁</a>
                        <span> | </span>
                        <a href="/about">關於我們</a>
                    </nav>
                    <input type="text"/>
                    <hr/>
                    <Route path="/" component={Home} exact />
                    <Route path="/about" component={About}/>

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.02-4
Branch:branch4

commit description:v3.02-4-example02-4(應用場景(一)- 使用BrowserRouter組件,帶來的丟失狀態的問題)

tag:v3.02-4

2.3.1.4.5 example02-5

怎麼處理呢?不要用原生的a標籤了!推薦使用它的link組件。

    import React from 'react';
 
    import {Route, Link} from 'react-router-dom';
 
    import Home from './views/Home';
    import About from './views/About';
 
    class App extends React.Component {
 
        constructor(props) {
            super(props);
        }
 
        render() {

            return (
                <div className="App">
                    <h1>React路由</h1>
                    <hr/>
                    <nav>
                        <Link to="/">首頁</Link>
                        <span> | </span>
                        <Link to="/about">關於我們</Link>
                    </nav>
                    <hr/>
                    <input type="text"/>
                    <Route path="/" component={Home} exact />
                    <Route path="/about" component={About}/>
                </div>
            )
        }
 
    }
 
    export default App;

link幫助我們生成的a標籤,還監聽了a標籤的點擊事件,阻止了a的默認跳轉行爲,同時在它的內部又觸發了html5historyapi,修改了url,再根據修改的url,顯示對應的組件。

同時不論是用HashRouter還是BrowserRouter,link組件都會幫我們生成對應a標籤,我們無需關係是否加#了!

仔細觀察,a標籤裏有點擊事件。

image-20200606161531402

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.02-5
Branch:branch4

commit description:v3.02-5-example02-5(應用場景(一)- 使用BrowserRouter組件,解決帶來的丟失狀態的問題)

tag:v3.02-5

2.3.2 應用場景(二)

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

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
    }
  ]
}

2.3.2.2 傳遞 props

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

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

2.3.2.2.1 example03

首先我們希望把商品列表顯示在home主頁當中,因此我們需要往該組件中傳數據了。而目前我們home主件是作爲一個屬性傳給Route的。

            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
                    }
                ]
            }
{/*
    下面這種寫法,items 是傳遞給了 Route ,而不是 Home
*/}
<Route path="/" component={Home} exact items={this.state.items} />
// 如果是我們自己寫的數據,我們傳參是如下形式:
<Home items={this.state.items} / >

我們在首頁組件中打印一下props,我們看看一下:

react-Novice05\app\src\App.js

import React from 'react';

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

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

class App extends React.Component {

    constructor(props) {
        super(props);

        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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>
                <nav>
                    <Link to="/">首頁</Link>
                    <span> | </span>
                    <Link to="/about">關於我們</Link>
                </nav>
                <hr/>
                <Route path="/" component={Home} exact items={this.state.items}/>
                <Route path="/about" component={About}/>
            </div>
        )
    }

}

export default App;

react-Novice05\app\src\views\Home.js

import React from 'react';

export default class Home extends React.Component {

    render() {
        console.log(this.props);
        return (
            <div>
                <h2>商品列表</h2>
            </div>
        );
    }

}

小迪找了很久,也確實沒找到items

image-20200705202530360

上面這種寫法,items 是傳遞給了 Route ,而不是 Home。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.03
Branch:branch4

commit description:v3.03-example03(應用場景(二)- 如何給路由組件的組件參數傳值?)

tag:v3.03

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

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

2.3.2.2.2.1 example04

以上是不成功的,那路由組件怎麼給組件參數傳值呢?我們修改一下:

傳遞 propscomponent指定的 組件
Route 還提供了一個 render屬性,傳入一個函數,函數返回的值就是 等同於 Component指定的組件渲染的結果。

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

    class App extends React.Component {
 
        constructor(props) {
            super(props);
 
            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
                    }
                ]
            }
        }
 
        render() {
            return (
                <div className="App">
                    <h1>React路由</h1>
                    <hr/>
                    <nav>
                        <Link to="/">首頁</Link>
                        <span> | </span>
                        <Link to="/about">關於我們</Link>
                    </nav>
                    <hr/>
                    <Route path="/" exact  render={() => {
                        return <div>我是render出來的</div>
                    }} />
                    <Route path="/about" component={About}/>  
                </div>
            )
        }
 
    }
 
    export default App;
image-20200606163419843

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.04-1
Branch:branch4

commit description:v3.04-1-example04-1(應用場景(二)- 如何給路由組件的組件參數傳值?傳遞 props 給 component 指定的 組件)

tag:v3.04-1

app.js

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

    class App extends React.Component {
 
        constructor(props) {
            super(props);
 
            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
                    }
                ]
            }
        }
 
        render() {
 
            return (
                <div className="App">
 
                    <h1>React路由</h1>
                    <hr/>
                    <nav>
                        <Link to="/">首頁</Link>
                        <span> | </span>
                        <Link to="/about">關於我們</Link>
                    </nav>
                    <hr/>
                    <Route path="/" exact render={() => {
                        return <Home items={this.state.items} />
                    }} />
                    <Route path="/about" component={About}/>      
                </div>
            )
        }
 
    }
 
    export default App;

src/views/Home.js

import React from 'react';
 
export default class Home extends React.Component {
 
    render() {
        console.log(this.props);
        return (
            <div>
                <h2>商品列表</h2>
            </div>
        );
    }
}

image-20200606163850087

可以簡寫:

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

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.04-2
Branch:branch4

commit description:v3.04-2-example04-2(應用場景(二)- 如何給路由組件的組件參數傳值?傳遞 props 給 component 指定的 組件)

tag:v3.04-2

完善列表

src/css.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 {Route, Link} from 'react-router-dom';
 
    import './css.css';
 
    import Home from './views/Home';
    import About from './views/About';

    class App extends React.Component {
 
        constructor(props) {
            super(props);
 
            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
                    }
                ]
            }
        }
 
        render() {
 
            return (
                <div className="App">
                    <h1>React路由</h1>
                    <hr/>
 
                    <nav>
                        <Link to="/">首頁</Link>
                        <span> | </span>
                        <Link to="/about">關於我們</Link>
                    </nav>
                    <hr/>
                    <Route path="/" exact render={() => <Home items={this.state.items} />} />
                    <Route path="/about" component={About}/>
                </div>
            )
        }
    }
    export default App;

src/views/Home.js

import React from 'react';
 
import Item from '../components/Item.js';
 
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 => <Item item={item} key={item.id} /> )
                    }
                </ul>
            </div>
        );
    }
 
}

src/components/Item.js

import React from "react";
import {Link} from "react-router-dom";
 
export default class Item extends React.Component{
 
    render() {
        let {item} = this.props;
 
        return(
            <li>
                <span>
                    <a href="">{item.name}</a>
                </span>
                {/*保留兩位小數*/}
                <span>{(item.price / 100).toFixed(2)}</span>
            </li>
        );
    }
 
}

image-20200606181327899

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.04-3
Branch:branch4

commit description:v3.04-3-example04-3(應用場景(二)- 完善首頁列表)

tag:v3.04-3

2.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>
        );
    }
 
}
2.3.2.3.1 example05

完善詳情頁

App.js

 
 
    import React from 'react';
 
    import {Route, Link} from 'react-router-dom';
 
    import './css.css';
 
    import Home from './views/Home';
    import About from './views/About';
    import View from "./views/View";
 
    class App extends React.Component {
 
        constructor(props) {
            super(props);
 
            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
                    }
                ]
            }
        }
 
        render() {
            return (
                <div className="App">
                    <h1>React路由</h1>
                    <hr/>
 
                    <nav>
                        <Link to="/">首頁</Link>
                        <span> | </span>
                        <Link to="/about">關於我們</Link>
                    </nav>
                    <hr/>

                    <Route path="/" exact render={() => <Home items={this.state.items} />} />
 
                    {/*商品詳情*/}
                    <Route path='/view' component={View}/>
 
                    <Route path="/about" component={About}/>
                </div>
            )
        }
 
    }   
    export default App;

src/components/Item.js

import React from "react";
import {Link} from "react-router-dom";
 
export default class Item extends React.Component{
 
    render() {
        let {item} = this.props;
 
        return(
            <li>
                <span>
                    <Link to={"/view/"}>{item.name}</Link>
                </span>
                {/*保留兩位小數*/}
                <span>{(item.price / 100).toFixed(2)}</span>
            </li>
        );
    }
 
}

src/views/View.js

import React from 'react';
 
export default class View extends React.Component {
 
    render() {
        return(
            <div>
                <h2>商品詳情 </h2>
                <hr/>
                <p>其它一些詳情信息...</p>
            </div>
        );
    }
 
}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.05
Branch:branch4

commit description:v3.05-example05(應用場景(二)- 增加點擊詳情功能)

tag:v3.05

如何往詳情頁裏傳遞參數呢?我們點擊的列表某一項需要把該項信息傳遞給詳情頁。

可能想到從item往上傳給home再傳給app,最終再給view。可想而知這樣傳參會非常噁心!

如果組件是頁面組件,那麼這個兩個不同的頁面組件之間的數據傳遞就不要通過傳統組件父子通信來做,數據要通過頁面(路由等方式來進行傳遞,或者loacl storage進行中轉,或者redux中轉,其實都是做統一存儲的)

2.3.2.3.2 params

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

2.3.2.4 動態路由

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

<Route path='/item/:id' component={Item} />
2.3.2.4.1 path-to-regexp

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

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

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

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

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

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

2.3.2.4.2 Route : props

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

2.3.2.5 路由組件

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

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

  • history : 對象,提供了一些用於操作路由的方法,類似原生 JavaScript 中的 history 對象
  • location : 對象,通過它可以獲取當前路由 URL 信息,類似原生 JavaScript 中的 history.location 對象
  • match : 對象,當前路由解析後的 URL 信息
2.3.2.5.1 example06

利用動態路由中轉頁面。

2.3.2.5.1.1 example06-1

下面的路由是動態,':'後面的 id 表示動態部分匹配到內容
當下 /view/XXXXX 才能匹配當前的路由

react-Novice05\app\src\App.js

import React from 'react';

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

import './css.css';

import Home from './views/Home';
import About from './views/About';
import View from "./views/View";

class App extends React.Component {

    constructor(props) {
        super(props);

        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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>

                <nav>
                    <Link to="/">首頁</Link>
                    <span> | </span>
                    <Link to="/about">關於我們</Link>
                </nav>
                <hr/>

                <Route path="/" exact render={() => <Home items={this.state.items} />} />
                <Route path='/view/:id' component={View}/>
                <Route path="/about" component={About}/>
            </div>
        )
    }

}

export default App;

我們發現http://localhost:3000/view頁面就看不到了,必須是這種形式:http://localhost:3000/view/參數頁面才能顯示,即滿足"/view/:id" 這個規則頁面才能匹配,這個參數就會在實際匹配當中賦值給id:id其實就是一個變量,去接收路由當中與之匹配的參數。

可是現在參數爲任何值都行,如果僅限數字呢?其實可以設置類似正則的規則。這裏設置後面必須是數字,且至少1個。

<Route path="/view/:id(\d+)" component={View} />

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.06-1
Branch:branch4

commit description:v3.06-1-example06-1(應用場景(二)- 利用動態路由中轉頁面)

tag:v3.06-1

2.3.2.5.1.2 example06-2

src/components/Item.js

import React from "react";
import {Link} from "react-router-dom";
 
export default class Item extends React.Component{
 
    render() {
        let {item} = this.props;
 
        return(
            <li>
                <span>
                    <Link to={"/view/" + item.id}>{item.name}</Link>
                </span>
                {/*保留兩位小數*/}
                <span>{(item.price / 100).toFixed(2)}</span>
            </li>
        );
    }
 
}

從動態路由 url中拿到:id對應的內容

src/views/View.js

import React from 'react';
 
export default class View extends React.Component {
 
    render() {
 
        console.log(this.props);
 
        return(
            <div>
                <h2>商品詳情 </h2>
                <p>其它一些詳情信息...</p>
            </div>
        );
    }
 
}

我們發現我們沒有往viewprops傳內容,怎麼多了很多東西。

image-20200606215049123

BrowserRouter是容器組件,這裏我們App嵌套在其內,它會給App注入東西。我們看下僞代碼:

BrowserRouter大致僞代碼:

    class BrowserRouter extends React.Component {
 
        render() {
            
            // 渲染子組件
 
            return this.props.children; // 返回子組件
        }
 
    }

App.js中用route僞代碼:

    class Route extends React.Component {
 
        render() {
             // component是我們在標籤上寫的組件屬性
 			// 增加一些對象
            this.props.component.props.history;
            this.props.component.props.match;
            this.props.component.props.location;
 
            return this.props.component; // 我們所寫的component屬性
        }
 
    }

如果一個組件是路由組件(路由直接綁定並訪問的組件),那麼該組件就會被自動注入(靠BrowserRouter容器組件注入)

  • history: 同(html5)history api 類似,可跳頁面

image-20200606215120325

  • match: 當前訪問的路由信息

image-20200606215200219

  • location: 等同 原生的location

image-20200606215228450

view.js

import React from 'react';
 
export default class View extends React.Component {
 
    render() {
 
        // console.log(this.props);
        let {id} = this.props.match.params;
        console.log(id)
 
 
        return(
            <div>
                <h2>商品詳情 </h2>
                <p>其它一些詳情信息...</p>
            </div>
        );
    }
 
}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.06-2
Branch:branch4

commit description:v3.06-2-example06-2(應用場景(二)- 利用動態路由中轉頁面—路由容器組件注入對象屬性)

tag:v3.06-2

2.3.2.5.2 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>;
}
2.3.2.5.2.1 example07

目前我們可以通過url拿到id,但視圖組件所需要的數據從何而來?我們現在需要拿着id,去數據中找到對應項,然後展示出來。後期可能直接給後端發請求,去要數據了,即前端不去做而是去向後端發請求。

app.js

 
 
    import React from 'react';
 
    import {Route, Link} from 'react-router-dom';
 
    import './css.css';
 
    import Home from './views/Home';
    import About from './views/About';
    import View from "./views/View";
 
 
    class App extends React.Component {
 
        constructor(props) {
            super(props);
 
            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
                    }
                ]
            }
        }
 
        render() {
 
            return (
                <div className="App">
                    <h1>React路由</h1>
                    <hr/>
                    <nav>
                        <Link to="/">首頁</Link>
                        <span> | </span>
                        <Link to="/about">關於我們</Link>
                    </nav>
                    <hr/>
                    <Route path="/" exact render={() => <Home items={this.state.items} />} />
                    <Route path="/view/:id(\d+)" render={() => {
                        return <View />
                    }} />
                    <Route path="/about" component={About}/>
                </div>
            )
        }
 
    }
 
    export default App;

視圖組件所需要的數據從何而來??????????????????

view.js

import React from 'react';
 
export default class View extends React.Component {
 
    render() {

        console.log(this.props);
        let {id} = this.props.match.params;
        console.log(id)
 
        return(
            <div>
                <h2>商品詳情 </h2>
                <p>其它一些詳情信息...</p>
            </div>
        );
    }
 
}

報錯(掛了)原因:View不在是路由組件了,那麼也就表示 Route 注入的 路由相關信息就沒有了,即history、 match、location等參數對象就沒了。

image-20200706111905225

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.07-1
Branch:branch4

commit description:v3.07-1-example07-1(應用場景(二)- 完善詳情頁,跳轉頁面報錯)

tag:v3.07-1

如果使用了render 函數,那麼路由注入的 history、match 等對象都作爲參數傳遞給了render函數了
render其實就是一個函數式的組件

                    <Route path="/view/:id(\d+)" render={(props) => {
                        return <View />
                        console.log(props);
                    }} />
                    <Route path="/about" component={About}/>

image-20200606223217569

src/App.js

 
 
    import React from 'react';
 
    import {Route, Link} from 'react-router-dom';
 
    import './css.css';
 
    import Home from './views/Home';
    import About from './views/About';
    import View from "./views/View";
 
    class App extends React.Component {
 
        constructor(props) {
            super(props);
 
            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
                    }
                ]
            }
        }
 
        render() {
            return (
                <div className="App">
                  
 
                    <nav>
                        <Link to="/">首頁</Link>
                        <span> | </span>
                        <Link to="/about">關於我們</Link>
                    </nav>
                    <hr/>
                    <Route path="/" exact render={() => <Home items={this.state.items} />} />
 
                    <Route path="/view/:id(\d+)" render={(props) => {
                        // es5 寫法太長了
                        // return <View match={props.match} history={props.history} location={props.location} />
                        // es6寫法比較簡練,解構(擴展運算符)
                        return <View {...props} items={this.state.items} />
                    }} />
                    <Route path="/about" component={About}/>
                </div>
            )
        }
 
    }
 
    export default App;

src/components/Item.js

import React from "react";
import {Link} from "react-router-dom";
 
export default class Item extends React.Component{
 
    render() {
        let {item} = this.props;
 
        return(
            <li>
                <span>
                    <Link to={"/view/" + item.id}>{item.name}</Link>
                </span>
                {/*保留兩位小數*/}
                <span>{(item.price / 100).toFixed(2)}</span>
            </li>
        );
    }
 
}

src/views/View.js

import React from 'react';
 
export default class View extends React.Component {
 
    render() {

        // 解構
        let {items, match: {params: {id}}} = this.props;
        id = Number(id);
 
        let item = items.find( v => v.id === id );
 
        console.log(items, id, item);
 
        return(
            <div>
                <h2>商品詳情 - {item.name}</h2>
                <p>其它一些詳情信息...</p>
            </div>
        );
    }
 
}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.07-2
Branch:branch4

commit description:v3.07-2-example07-2(應用場景(二)- 完善詳情頁,跳轉頁面最終版)

tag:v3.07-2

2.3.2.6 通信小結

  • 從動態路由 url中拿到 :id對應的內容

  • 視圖組件所需要的數據從何而來??????????????????(肯定涉及兩個位置,從a到b,如果只有一個組件,就談不上組件數據從何而來的問題了,即通信問題了)

  • 組件獲取數據的方式:

    • 組件的(參數傳遞)父子通信,由父級傳參把數據傳進來
    • 通過與該組件對應的 url傳遞過來,組件肯定與url是有關聯的,我們需要把組件需要的數據掛載到url上面,然後當組件渲染的時候,再去檢查url上某個位置的數據
    • 通過後端獲取:在組件的一些生命週期中發送請求(ajax
    • 通過本地存儲(不推薦,因爲它有大小限制)
    • 全局變量(內存),如把數據掛載到window底下,到數據渲染的時候,再從window下面取(一般情況下,是不推薦的,它類似於redux,除非有更好方式管理和維護全局變量,否則就容易亂套了)
    • …(還有很多方式,但只關注一點,凡是涉及到數據通信,當前能訪問的方式,如下)
  • 數據通信選擇何種方式

    • 如果是組件不是嵌套關係(如本例),我們選擇方案1,就會涉及到通信步驟太多的問題,你需要一層層的傳,會很繁瑣
    • 實際兩組件是嵌套關係,即父子級關係,或者兄弟關係(它們共同父級就在上一層,不太多),可用方案1
    • 方案2其實適合兩個組件不同時存在的時候,如有view,就沒item,即在一個頁面中兩個不同時顯示,兩個組件是互不見面的,但是可從urlview的數據帶給item,但僅限小批量的數據

2.3.3 應用場景(三)

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

2.3.3.1 通過 JavaScript 實現排序切換

Home.js

...
this.state = {
      sort: 'desc'
 
      this.changeSort = this.changeSort.bind(this);
}
...
changeSort({target: {value: sort}}) {
      this.setState({sort});
}
...
render() {
      let {items} = this.props;
      let {sort} = this.state;
      items = items.sort((a, b) => sort === 'asc'  ? a.price - b.price : b.price - a.price);
      return(
          ...
          <select value={sort} onChange={this.changeSort}>
          <option value="desc">從高到低</option>
          <option value="asc">從低到高</option>
          </select>
          ...
    )
}

問題:刷新頁面、分享 URL 都會丟失狀態

2.3.3.1.1 example08

js實現排序

2.3.3.1.1.1 example08-1

src/views/Home.js

import React from 'react';
 
import Item from '../components/Item.js';
 
export default class Home extends React.Component {
 
 
    render() {
 
 
        let {items} = this.props;
 
        items = items.sort( (a, b) => {
            return b.price - a.price;
        } );
 
 
        return (
            <div>
                <h2>商品列表</h2>
 
                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
 
                    {
                        items.map( item => <Item item={item} key={item.id} /> )
                    }
                </ul>
            </div>
        );
    }
 
}

降序排列:

image-20200606233558809

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.08-1
Branch:branch4

commit description:v3.08-1-example08-1(應用場景(三)- 用戶可以按照商品價格的高低進行選擇展示-js實現-簡單的降序排列)

tag:v3.08-1

2.3.3.1.1.2 example08-2
import React from 'react';
 
import Item from '../components/Item.js';
 
export default class Home extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.sort = this.sort.bind(this);
    }
 
    sort(e) {
        console.log(e.target.value);
 
        let {items} = this.props;
 
        items = items.sort( (a, b) => {
            return 'desc' === e.target.value ? b.price - a.price : a.price - b.price;
        } );
 
        this.setState({
            items
        });
    }
 
    render() {
 
        let {items} = this.props;
 
        return (
            <div>
                <h2>商品列表</h2>
 
                <select onChange={this.sort}>
                    <option value="desc">降序</option>
                    <option value="asc">升序</option>
                </select>
 
                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        items.map( item => <Item item={item} key={item.id} /> )
                    }
                </ul>
            </div>
        );
    }
 
}

發現沒有設置默認排序。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.08-2
Branch:branch4

commit description:v3.08-2-example08-2(應用場景(三)- 用戶可以按照商品價格的高低進行選擇展示-js實現-實現切換排序)

tag:v3.08-2

2.3.3.1.1.3 example08-3

我們需要先設置一下默認排序狀態,可以封裝一下:

import React from 'react';
 
import Item from '../components/Item.js';
 
export default class Home extends React.Component {
 
    constructor(props) {
        super(props);
 
        // 默認降序,賦給state
        this.state = {
            items: this.doSort('desc')
        };
 
        this.sort = this.sort.bind(this);
    }
 
    sort(e) {
        console.log(e.target.value);
 
        let items = this.doSort(e.target.value);
        this.setState({
            items
        });
    }
 
    doSort(type = 'desc') {
        let {items} = this.props;
 
        return items.sort( (a, b) => {
            return 'desc' === type ? b.price - a.price : a.price - b.price;
        } );
    }
 
 
    render() {
        // 取默認排序好的items
        let {items} = this.state;
 
        return (
            <div>
                <h2>商品列表</h2>
 
                <select onChange={this.sort}>
                    <option value="desc">降序</option>
                    <option value="asc">升序</option>
                </select>
 
                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        items.map( item => <Item item={item} key={item.id} /> )
                    }
                </ul>
            </div>
        );
    }
 
}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.08-3
Branch:branch4

commit description:v3.08-3-example08-3(應用場景(三)- 用戶可以按照商品價格的高低進行選擇展示-js實現-切換排序添加默認降序排序)

tag:v3.08-3

存在問題:

1、假如數據很多,就需要進行分頁了。

2、如果把排序好的頁面分享好別人,別人打開的頁面是默認降序排序,則可能拿到的頁面就不同了,所以我們這裏排序最好也保留狀態(通過url也能判斷出排序狀態)。(排序功能不要影響url地址本身,否則路由看不清楚了,即不影響url資源同時又可攜帶數據,因此需要用到queryString

2.3.3.2 通過路由實現排序切換

2.3.3.2.1 queryString

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

/api/users 這個地址代表與用戶有關的所有數據,假設數據有10000條,而只希望看到一部分的話,就相當於需要帶查詢條件,例如:/api/users ?page=1 queryString 其實就帶着某種和條件去獲取資源

迴歸正題:我們可以設置爲localhost:3000/?sort=desc

2.3.3.2.2 URLSearchParams

在原生 JavaScript 中內置了一個 URLSearchParams 的類,我們通過它可以很方便的操作 queryString

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

2.3.3.3 擴展

2.3.3.3.1 qs 庫

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

2.3.3.3.2 安裝
npm i -S qs
2.3.3.3.3 使用

search: 解析的字符串

ignoreQueryPrefix:是否忽略問號

import queryString from 'qs';
 
let qsTest = queryString.parse(search, {ignoreQueryPrefix: true});
let sort = qsTest.sort;
2.3.3.3.4 通過 JavaScript 編程的方式切換路由

除了使用 <Link> 組件像 a 一樣點擊跳轉路由,我們還可以通過編程的方式(JavaScript) 來切換路由

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

通過路由實現排序切換

2.3.3.3.5.1 example09-1

home.js

// 從 url 的 querystring 上來獲取
        console.log(window.location);

image-20200607220331225

        // 從 url 的 querystring 上來獲取
        console.log(window.location.search);

image-20200607220925853

我們把問號截取掉!

    // 從 url 的 querystring 上來獲取
    console.log(window.location.search.substring(1));
image-20200607221137347

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.09-1
Branch:branch4

commit description:v3.09-1-example09-1(應用場景(三)- 用戶可以按照商品價格的高低進行選擇展示-獲取querystring)

tag:v3.09-1

2.3.3.3.5.2 example09-2

以上代碼是:key=value的格式,我們如何取到value呢?我們可以將其轉爲對象去取,或者使用第三方的庫(如:qs庫)。

原生方法:

    let queryString = window.location.search.substring(1);
    // 作用:將queryString轉爲對象形式
    let qs = new URLSearchParams(queryString);
    console.log(qs);

image-20200607222007936

get方法取值:

    let queryString = window.location.search.substring(1);
    // 作用:將queryString轉爲對象形式
    let qs = new URLSearchParams(queryString);
    console.log(qs.get('sort'));

image-20200607222120777

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.09-2
Branch:branch4

commit description:v3.09-2-example09-2(應用場景(三)- 用戶可以按照商品價格的高低進行選擇展示-原生js獲取querystring中的值)

tag:v3.09-2

2.3.3.3.5.3 example09-3

queryString: 解析的字符串

ignoreQueryPrefix:是否忽略問號

    import qs from 'qs';        

    let queryString = window.location.search.substring(1);
    // 作用:將queryString轉爲對象形式
    let qsTest = qs.parse(queryString, {ignoreQueryPrefix: true});
    let sort = qsTest.sort;
    console.log(sort);

image-20200607222120777

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.09-3
Branch:branch4

commit description:v3.09-3-example09-3(應用場景(三)- 用戶可以按照商品價格的高低進行選擇展示-qs庫獲取querystring中的值)

tag:v3.09-3

2.3.3.3.5.4 example09-4

以上我們是通過原生js獲取的queryString,同時還可以通過組件對象獲取。剛剛我們講過路由組件渲染的時候,如果一個組件是路由組件(路由直接綁定並訪問的組件),那麼該組件就會被自動注入:

  • history: 同(html5)history api類似,可跳頁面
  • match: 當前訪問的路由信息
  • location: 等同 原生的 location

react-Novice05\app\src\App.js

import React from 'react';

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

import './css.css';

import Home from './views/Home';
import About from './views/About';
import View from "./views/View";

class App extends React.Component {

    constructor(props) {
        super(props);

        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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>

                <nav>
                    <Link to="/">首頁</Link>
                    <span> | </span>
                    <Link to="/about">關於我們</Link>
                </nav>
                <hr/>

                <Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                <Route path="/view/:id(\d+)" render={(props) => {
                    return <View {...props} items={this.state.items} />
                }} />
                <Route path="/about" component={About}/>
            </div>
        )
    }

}

export default App;

react-Novice05\app\src\views\Home.js

import React from 'react';

import Item from '../components/Item.js';
import qs from 'qs';

export default class Home extends React.Component {

    constructor(props) {
        super(props);

        // 默認降序,賦給state
        this.state = {
            items: this.doSort('desc')
        };

        this.sort = this.sort.bind(this);
    }

    sort(e) {
        console.log(e.target.value);

        let items = this.doSort(e.target.value);
        this.setState({
            items
        });
    }

    doSort(type = 'desc') {
        let {items} = this.props;

        return items.sort( (a, b) => {
            return 'desc' === type ? b.price - a.price : a.price - b.price;
        } );
    }

    render() {

        // 取默認排序好的items
        let {items} = this.state;

        console.log(this.props)

        let queryString = window.location.search.substring(1);
        // 作用:將queryString轉爲對象形式
        let qsTest = qs.parse(queryString, {ignoreQueryPrefix: true});
        let sort = qsTest.sort;

        return (
            <div>
                <h2>商品列表</h2>

                <select onChange={this.sort}>
                    <option value="desc">降序</option>
                    <option value="asc">升序</option>
                </select>

                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        items.map( item => <Item item={item} key={item.id} /> )
                    }
                </ul>
            </div>
        );
    }

}

可在locationsearch中取值。

image-20200706140555801

        let {location} = this.props;

        // let queryString = window.location.search.substring(1);
        let queryString = location.search.substring(1);
        // 作用:將queryString轉爲對象形式
        let qsTest = qs.parse(queryString, {ignoreQueryPrefix: true});
        let sort = qsTest.sort;
        console.log(sort);

image-20200607222120777

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.09-4
Branch:branch4

commit description:v3.09-4-example09-4(應用場景(三)- 用戶可以按照商品價格的高低進行選擇展示-通過組件對象獲取queryString)

tag:v3.09-4

2.3.3.3.5.5 example09-5

完善排序切換選項卡功能:

用戶在select標籤中選擇的時候,我們不希望它是受控組件(這裏的值與state進行綁定),目前sort變量的值並不在組件內部,其實用戶選擇排序的時候,沒有必要去修改sort變量的值,只需要跳頁面就行了。因此我們給它綁定一個defaultvalue屬性(綁定非受控)即可。

import React from 'react';

import Item from '../components/Item.js';
import qs from 'qs';

export default class Home extends React.Component {

    constructor(props) {
        super(props);

        // 默認降序,賦給state
        this.state = {
            items: this.doSort('desc')
        };

        this.sort = this.sort.bind(this);
    }

    sort(e) {
        // let items = this.doSort(e.target.value);
        // this.setState({
        //     items
        // });
        // 跳轉頁面
        let {history} = this.props;
        history.push(`/?sort=${e.target.value}`)
    }

    doSort(type = 'desc') {
        let {items} = this.props;

        return items.sort( (a, b) => {
            return 'desc' === type ? b.price - a.price : a.price - b.price;
        } );
    }

    render() {

        // 取默認排序好的items
        let {items} = this.state;
        let {location} = this.props;

        // let queryString = window.location.search.substring(1);
        let queryString = location.search.substring(1);
        // 作用:將queryString轉爲對象形式
        let qsTest = qs.parse(queryString, {ignoreQueryPrefix: true});
        let sort = qsTest.sort || 'desc';

        this.doSort(sort);

        return (
            <div>
                <h2>商品列表</h2>

                <select defaultValue={sort} onChange={this.sort}>
                    <option value="desc">降序</option>
                    <option value="asc">升序</option>
                </select>

                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        items.map( item => <Item item={item} key={item.id} /> )
                    }
                </ul>
            </div>
        );
    }

}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.09-5
Branch:branch4

commit description:v3.09-5-example09-5(應用場景(三)- 用戶可以按照商品價格的高低進行選擇展示-完善排序切換選項卡功能最終版)

tag:v3.09-5

2.3.4 應用場景(四)

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

2.3.4.1 NavLink 組件

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

2.3.4.1.1 activeStyle

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

2.3.4.1.2 activeClassName

activeStyle 類似,但是激活的是 className

2.3.4.1.3 isActive(類似回調,可以處理一些更爲複雜的情況)

默認情況下,匹配的是 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>
2.3.4.1.4 exact

精確匹配

2.3.4.2 example10

NavLink 組件(導航組件)

2.3.4.2.1 example10-1

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

react-Novice05\app\src\App.js

import React from 'react';

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

import './css.css';

import Home from './views/Home';
import About from './views/About';
import View from "./views/View";
import NavLink from "react-router-dom/modules/NavLink";

class App extends React.Component {

    constructor(props) {
        super(props);

        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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>

                <nav>
                    <NavLink to="/" activeStyle={{color: 'red'}}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/about" activeStyle={{color: 'red'}}>關於我們</NavLink>
                </nav>
                <hr/>

                <Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                <Route path="/view/:id(\d+)" render={(props) => {
                    return <View {...props} items={this.state.items} />
                }} />
                <Route path="/about" component={About}/>
            </div>
        )
    }

}

export default App;

不過,明顯發現存在問題!爲什麼點擊關於我們後,首頁仍然會高亮!因爲默認情況下,它屬於一種模糊匹配,即非精確匹配。因爲此時"/about"也同時滿足"/" 的規則,所以"/about"會把兩種都匹配上!

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.10-1
Branch:branch4

commit description:v3.10-1-example10-1(應用場景(四)- 頁面頂部的導航加上高亮效果-未精確匹配)

tag:v3.10-1

2.3.4.2.2 example10-2

完善精確匹配:

學會看文檔

image-20200608001314519

image-20200608001354875

image-20200608001415397

image-20200608001424650

image-20200608001431435

import React from 'react';

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

import './css.css';

import Home from './views/Home';
import About from './views/About';
import View from "./views/View";

class App extends React.Component {

    constructor(props) {
        super(props);

        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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>

                <nav>
                    <NavLink to="/" activeStyle={{color: 'red'}} exact={true}   >首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/about" activeStyle={{color: 'red'}}>關於我們</NavLink>
                </nav>
                <hr/>

                <Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                <Route path="/view/:id(\d+)" render={(props) => {
                    return <View {...props} items={this.state.items} />
                }} />
                <Route path="/about" component={About}/>
            </div>
        )
    }

}

export default App;

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.10-2
Branch:branch4

commit description:v3.10-2-example10-2(應用場景(四)- 頁面頂部的導航加上高亮效果-精確匹配)

tag:v3.10-2

2.3.4.2.3 example10-3

isActive(類似回調,可以處理一些更爲複雜的情況):

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

isActive屬性什麼時候使用呢?

以上example10-2的原理是urlto屬性按照某種規則進行比較,如果比較後滿足規則,則激活設置的樣式。

如果我們點進詳情(即訪問內頁),此時的url與我們上述的to屬性都匹配不上了,此時怎麼高亮呢?這個時候就不能用它了,得需要我們自己去定規則了。

<nav>
    <NavLink to="/" exact={true} activeStyle={{color: 'red'}} isActive={(match, location) => {
        // match當前匹配的 location 當前地址欄信息
        console.log(match, location);
    }}>首頁</NavLink>
    <span> | </span>
    <NavLink to="/about" activeStyle={{color: 'red'}} >關於我們</NavLink>
</nav>

當點擊詳情頁,matchnull,表示當前沒有匹配項,即當前訪問的地址是不匹配的(與當前的屬性to)。

                <nav>
                    <NavLink to="/" exact={true} activeStyle={{color: 'red'}} isActive={(match, location) => {
                        // match當前匹配的 location 當前地址欄信息
                        // console.log(match, location);
                        // match 爲真後面無需處理 否則以'/view'命名開始
                        return match || location.pathname.startsWith('/view');
                    }}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/about" activeStyle={{color: 'red'}}>關於我們</NavLink>
                </nav>

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.10-3
Branch:branch4

commit description:v3.10-3-example10-3(應用場景(四)- 頁面頂部的導航加上高亮效果-完成)

tag:v3.10-3

2.3.5 應用場景(五)

當用戶訪問不存在的路由,我們需要提供一個反饋頁面,也就是 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 路由組件渲染

2.3.5.1 Switch 組件

該組件只會渲染首個被匹配的組件

<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>

2.3.5.2 example11

實現 404(友好頁面)

2.3.5.2.1 example11-1

src/views/NotFound.js

import React from 'react';
 
export default class NotFound extends React.Component {
 
 
    render() {
        return (
            <div>
                <h1>Not Found - 404</h1>
            </div>
        );
    }
 
}

App.js

<Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                    <Route path="/view/:id(\d+)" render={(props) => {
                        return <View {...props} items={this.state.items} />
                    }} />
                    <Route path="/about" component={About}/>
                    <Route component={NotFound} />

每一個頁面均包含了404頁面組件。因爲沒有設置path,就代表實際默認爲通配符,可匹配任何路徑,即每一個頁面均包含了404頁面組件。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.11-1
Branch:branch4

commit description:v3.11-1-example11-1(應用場景(五)- 實現 404(友好頁面),遇到問題)

tag:v3.11-1

2.3.5.2.2 example11-2

默認react的路由具有穿透性

js當中的switch語句,這裏去掉break,則打印bc,滿足第一個變量a的值之後,如果沒有break,則繼續往下穿透。我們路由穿透的概念的就與這個類似,因此所有頁面都顯示。

let a = 2;
switch (a) {
    case 1:
        console.log('a');
        // break;
    case 2:
        console.log('b');
        // break;
    case 3:
        console.log('c');
        // break;
}

我們用switch組件來解決以上問題

                <Switch>
                    <Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                    <Route path="/view/:id(\d+)" render={(props) => {
                        return <View {...props} items={this.state.items} />
                    }} />
                    <Route path="/about" component={About}/>
                    <Route component={NotFound} />
                </Switch>

如果把notfound組件放在最前面,則所有頁面都是404,那是應爲它是通配的,沒設置規則,任何頁面都符合,因此在實際開發中要十分注意這種順序問題。(這塊代碼較爲簡單,小迪就不git了)

                <Switch>
                    <Route component={NotFound} />
                    <Route path="/" exact render={(props) => {
                        return <Home items={this.state.items} {...props} />
                    }} />
 
                    <Route path="/view/:id(\d+)" render={(props) => {
                        return <View {...props} items={this.state.items} />
                    }} />
                    <Route path="/about" component={About}/>
 
                </Switch>

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.11-2
Branch:branch4

commit description:v3.11-2-example11-2(應用場景(五)- 實現 404(友好頁面),完成)

tag:v3.11-2

2.3.6 應用場景(六)

現在,我們要給用戶增加一個購物車的功能

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: 'zs',
        password: '123'
      },
      {
        id: 2,
        username: 'ls',
        password: '123'
      },
      {
        id: 3,
        username: 'ww',
        password: '123'
      },
      {
        id: 4,
        username: 'gg',
        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>
      )
}

2.3.6.1 example12

實現購物界面

react-Novice05\app\src\views\Cart.js

import React from 'react';

export default class Cart extends React.Component {

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

}

react-Novice05\app\src\App.js

import React from 'react';

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

import './css.css';

import Home from './views/Home';
import About from './views/About';
import View from "./views/View";
import NotFound from "./views/NotFound";
import Cart from "./views/Cart";

class App extends React.Component {

    constructor(props) {
        super(props);

        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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>

                <nav>
                    <NavLink to="/" exact={true} activeStyle={{color: 'red'}} isActive={(match, location) => {
                        // match當前匹配的 location 當前地址欄信息
                        // console.log(match, location);
                        // match 爲真後面無需處理 否則以'/view'命名開始
                        return match || location.pathname.startsWith('/view');
                    }}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/cart" activeStyle={{color:'red'}} exact>購物車</NavLink>
                    <span> | </span>
                    <NavLink to="/about" activeStyle={{color: 'red'}}>關於我們</NavLink>
                </nav>
                <hr/>
                <Switch>
                    <Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                    <Route path="/view/:id(\d+)" render={(props) => {
                        return <View {...props} items={this.state.items} />
                    }} />
                    <Route path="/cart" component={Cart}/>
                    <Route path="/about" component={About}/>
                    <Route component={NotFound} />
                </Switch>
            </div>
        )
    }

}

export default App;

image-20200608133532244

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.12
Branch:branch4

commit description:v3.12-example12(應用場景(六)- 實現購物界面)

tag:v3.12

2.3.6.2 Redirect 組件

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

index.js

<Route path='/cart' render={props => {
    if (this.state.userInfo.uid > 0) {
              return <Cart />;
    } else {
              // return <Login />;
              return <Redirect to='/login' />;
    }
}}  />
2.3.6.2.1 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>
        );
    }
 
}
2.3.6.2.2 example13

然而根據需求,其實是有了登錄纔能有購物車這個功能的,我們暫時不實現一個完整的登錄註冊了,暫時模擬一個。

我們訪問購物車,不能再像上述直接訪問了,而是判斷用戶。

react-Novice05\app\src\views\Login.js

import React from 'react';

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

import './css.css';

import Home from './views/Home';
import About from './views/About';
import View from "./views/View";
import NotFound from "./views/NotFound";
import Cart from "./views/Cart";
import Redirect from "react-router-dom/es/Redirect";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            // 用戶信息
            userInfo: {
                id: 0,
                username: ''
            },

            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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>

                <nav>
                    <NavLink to="/" exact={true} activeStyle={{color: 'red'}} isActive={(match, location) => {
                        // match當前匹配的 location 當前地址欄信息
                        // console.log(match, location);
                        // match 爲真後面無需處理 否則以'/view'命名開始
                        return match || location.pathname.startsWith('/view');
                    }}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/cart" activeStyle={{color:'red'}} exact>購物車</NavLink>
                    <span> | </span>
                    <NavLink to="/about" activeStyle={{color: 'red'}}>關於我們</NavLink>
                </nav>
                <hr/>
                <Switch>
                    <Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                    <Route path="/view/:id(\d+)" render={(props) => {
                        return <View {...props} items={this.state.items} />
                    }} />
                    <Route path="/cart" component={() => {
                        if (this.state.userInfo.id > 0) {
                            return <Cart />;
                        } else {
                            // 重定向組件
                            return <Redirect to='/login' />;
                        }
                    }}/>
                    <Route path="/login" component={Login}/>
                    <Route path="/about" component={About}/>
                    <Route component={NotFound} />
                </Switch>
            </div>
        )
    }

}

export default App;

react-Novice05\app\src\App.js

import React from 'react';

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

import './css.css';

import Home from './views/Home';
import About from './views/About';
import View from "./views/View";
import NotFound from "./views/NotFound";
import Cart from "./views/Cart";
import Redirect from "react-router-dom/es/Redirect";
import Login from "./views/Login";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            // 用戶信息
            userInfo: {
                id: 0,
                username: ''
            },

            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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>

                <nav>
                    <NavLink to="/" exact={true} activeStyle={{color: 'red'}} isActive={(match, location) => {
                        // match當前匹配的 location 當前地址欄信息
                        // console.log(match, location);
                        // match 爲真後面無需處理 否則以'/view'命名開始
                        return match || location.pathname.startsWith('/view');
                    }}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/cart" activeStyle={{color:'red'}} exact>購物車</NavLink>
                    <span> | </span>
                    <NavLink to="/about" activeStyle={{color: 'red'}}>關於我們</NavLink>
                </nav>
                <hr/>
                <Switch>
                    <Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                    <Route path="/view/:id(\d+)" render={(props) => {
                        return <View {...props} items={this.state.items} />
                    }} />
                    <Route path="/cart" component={() => {
                        if (this.state.userInfo.id > 0) {
                            return <Cart />;
                        } else {
                            // 重定向組件
                            return <Redirect to='/login' />;
                        }
                    }}/>
                    <Route path="/login" activeStyle={{color:'red'}} exact component={Login}/>
                    <Route path="/about" component={About}/>
                    <Route component={NotFound} />
                </Switch>
            </div>
        )
    }

}

export default App;

        this.state = {
            // 用戶信息
            userInfo: {
                id: 1,
                username: ''
            },
            ......
        }

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.13
Branch:branch4

commit description:v3.13-example13(應用場景(六)- 實現購物界面加登錄權限控制)

tag:v3.13

2.3.7 應用場景(七)

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

// 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() {
 
        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);

2.3.7.1 withRouter 組件(高階組件)

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

2.3.7.2 example14

實現分頁功能。

2.3.7.2.1 example14-1

分頁框子。

react-Novice05\app\src\components\Pagination.js

import React from 'react';
import PropTypes from 'prop-types';

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

class Pagination extends React.Component {

    static defaultProps = {
        pages: 1, // 總頁數
        page: 1 // 當前頁
    }

    // 屬性驗證
    static propTypes = {
        pages: PropTypes.number,
        page: PropTypes.number
    }

    render() {
        let {pages, page} = this.props;

        return (
            // 根據當前頁,渲染a標籤
            <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={e=>{

                }} /></div>
        );
    }

}
export default Pagination;

react-Novice05\app\src\App.js

import React from 'react';

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

import './css.css';

import Home from './views/Home';
import About from './views/About';
import View from "./views/View";
import NotFound from "./views/NotFound";
import Cart from "./views/Cart";
import Redirect from "react-router-dom/es/Redirect";
import Login from "./views/Login";
import Pagination from "./components/Pagination";

class App extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            // 用戶信息
            userInfo: {
                id: 1,
                username: ''
            },

            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
                }
            ]
        }
    }

    render() {
        return (
            <div className="App">
                <h1>React路由</h1>
                <hr/>

                <nav>
                    <NavLink to="/" exact={true} activeStyle={{color: 'red'}} isActive={(match, location) => {
                        // match當前匹配的 location 當前地址欄信息
                        // console.log(match, location);
                        // match 爲真後面無需處理 否則以'/view'命名開始
                        return match || location.pathname.startsWith('/view');
                    }}>首頁</NavLink>
                    <span> | </span>
                    <NavLink to="/cart" activeStyle={{color:'red'}} exact>購物車</NavLink>
                    <span> | </span>
                    <NavLink to="/about" activeStyle={{color: 'red'}}>關於我們</NavLink>
                </nav>
                <hr/>
                <Switch>
                    <Route path="/" exact render={(props) => <Home items={this.state.items} {...props} />} />
                    <Route path="/view/:id(\d+)" render={(props) => {
                        return <View {...props} items={this.state.items} />
                    }} />
                    <Route path="/cart" component={() => {
                        if (this.state.userInfo.id > 0) {
                            return <Cart />;
                        } else {
                            // 重定向組件
                            return <Redirect to='/login' />;
                        }
                    }}/>
                    <Route path="/login" activeStyle={{color:'red'}} exact component={Login}/>
                    <Route path="/about" component={About}/>
                    <Route component={NotFound} />
                </Switch>

                <hr/>
                <Pagination page={1} pages={10} />
            </div>
        )
    }

}

export default App;

image-20200608140350916

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.14-1
Branch:branch4

commit description:v3.14-1-example14-1(應用場景(七)- 分頁框子)

tag:v3.14-1

2.3.7.2.2 example14-2

把分頁和排序均加進url中

如:localhost/page=2&sort=desc

上述的分頁組件寫錯位置了,應該寫在home組件中,否則關於我們的組件中下方也會分頁了。

react-Novice05\app\src\views\Home.js

import React from 'react';

import Item from '../components/Item.js';
import qs from 'qs';
import Pagination from "../components/Pagination";

export default class Home extends React.Component {

    constructor(props) {
        super(props);

        // 默認降序,賦給state
        this.state = {
            items: this.doSort('desc')
        };

        this.sort = this.sort.bind(this);
    }

    sort(e) {
        // let items = this.doSort(e.target.value);
        // this.setState({
        //     items
        // });
        // 跳轉頁面
        let {history} = this.props;
        history.push(`/?sort=${e.target.value}`)
    }

    doSort(type = 'desc') {
        let {items} = this.props;

        return items.sort( (a, b) => {
            return 'desc' === type ? b.price - a.price : a.price - b.price;
        } );
    }

    render() {

        // 取默認排序好的items
        let {items} = this.state;
        let {location} = this.props;

        // let queryString = window.location.search.substring(1);
        let queryString = location.search.substring(1);
        // 作用:將queryString轉爲對象形式
        let qsTest = qs.parse(queryString, {ignoreQueryPrefix: true});
        let sort = qsTest.sort || 'desc';

        this.doSort(sort);
        let page = Number(qsTest.page) || 1;

        return (
            <div>
                <h2>商品列表</h2>

                <select defaultValue={sort} onChange={this.sort}>
                    <option value="desc">降序</option>
                    <option value="asc">升序</option>
                </select>

                <ul className="item-list">
                    <li className="head">
                        <span>名稱</span>
                        <span>價格</span>
                    </li>
                    {
                        items.map( item => <Item item={item} key={item.id} /> )
                    }
                </ul>
                <hr/>

                <div>
                    <Pagination page={page} pages={10} />
                </div>
            </div>
        );
    }

}

image-20200608140350916

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.14-2
Branch:branch4

commit description:v3.14-2-example14-2(應用場景(七)- 分頁框子,應該放在Home中)

tag:v3.14-2

2.3.7.2.3 example14-3

完善分頁的跳轉某頁的功能。

react-Novice05\app\src\components\Pagination.js

                {/*完善分頁的跳轉某頁的功能*/}
                <input type="text" className="goto" onKeyDown={e=>{
                    if (e.target.value !== '' && e.keyCode == 13) {
                        // 跳轉頁面:刷新url
                        history.push('/?page=' + e.target.value);
                    }
                }} />

提示不能夠讀取push屬性是undefined,說明其實目前history屬性實際上是undefinedhistory在這裏是有問題的。當前分頁組件是嵌套在另一個頁面組件中的,因此它是不存在路由對象的。

image-20200608142143388

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.14-3
Branch:branch4

commit description:v3.14-3-example14-3(應用場景(七)- 完善分頁的跳轉某頁的功能,但報錯了)

tag:v3.14-3

2.3.7.2.4 example14-4

因爲home組件是有路由對象的,把home組件的history傳過去即可。

                <div>
                    <Pagination history={this.props.history} page={page} pages={10} />
 
                    {/*<Pagination page={page} pages={10} />*/}
                </div>

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v3.14-4
Branch:branch4

commit description:v3.14-4-example14-4(應用場景(七)- 完善分頁的跳轉某頁的功能,解決報錯問題)

tag:v3.14-4

但是這種做法是極不推薦的,假如後期修改,home組件中嵌套一個a組件,a組件再嵌套b組件,b再套分頁組件,這樣你需要一層層的傳,邏輯會很繁瑣。

這個時候我們可以依靠withRouter組件(高階組件)來解決這個問題了。

2.3.7.2.5 example14-5

通過 withRouterPagination 進行包裝,withRouter內部會把路由有關的幾個對象注入到 Pagination 組件的 props 中,並返回 Pagination

高階組件 - 高階函數

react-Novice05\app\src\components\Pagination.js

import {Link, withRouter} from 'react-router-dom';
......
export default withRouter(Pagination);

react-Novice05\app\src\views\Home.js

                <div>
                    <Pagination page={page} pages={10} />
                </div>

commit description:v3.14-5-example14-5(應用場景(七)- 完善分頁的跳轉某頁的功能,完成)

tag:v3.14-5

我們可以在react官網搜索高階組件HOC

高階組件是參數爲組件,返回值爲新組件的函數。即傳進去一個組件,就出來一個組件。

但是出來的新組件比起原組件,經過高階函數,進行了擴容。

image-20200608143502856

往下翻可看到源碼:

// 此函數接收一個組件...
function withSubscription(WrappedComponent, selectData) {
  // ...並返回另一個組件...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }
 
    componentDidMount() {
      // ...負責訂閱相關的操作...
      DataSource.addChangeListener(this.handleChange);
    }
 
    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }
 
    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }
 
    render() {
      // ... 並使用新數據渲染被包裝的組件!
      // 請注意,我們可能還會傳遞其他屬性
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

主要看返回值,這裏其實就相當於在內部引入了...this.props,就和我們自己最開始自己引入props一樣。

react的基礎就差不多了,剩下內容小迪以後再擴展。



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