七天接手react專案 系列 —— react 路由

彭加李發表於2022-03-23

其他章節請看:

七天接手react專案 系列

react 路由

本篇首先講解路由原理,接著以一個基礎路由示例為起點講述路由最基礎的知識,然後講解巢狀路由、路由傳參,最後講解路由元件和一般元件的區別,以及程式設計式導航。

Tip:我們要接手的 react 專案是:spug_web

什麼是路由

路由就是根據不同的 url(網址) 返回不同的頁面(資料)。如果這個工作在後端做(或者說由後端程式設計師做),就是後端路由;在前端做就是前端路由。

平時總說的 SPA(單頁面應用)就是前後端分離的基礎上,再加一層前端路由。

react 路由原理

下面通過一個js 庫(history)來演示一下路由的原理:

<body>
    請在瀏覽器控制檯下體驗!
    <!-- https://github.com/remix-run/history/blob/dev/docs/installation.md -->
    <script src="https://unpkg.com/history/umd/history.production.min.js"></script>
    <script>
        let myHistory = window.HistoryLibrary.createBrowserHistory()
        let unlisten = myHistory.listen(({ location, action }) => {
            console.log(action, location.pathname)
        });
    </script>
</body>

訪問頁面,瀏覽器 url 為 http://127.0.0.1:5500/public/test.html

Tip:筆者在 vscode 中安裝 “open in browser” 外掛,直接右鍵選擇 “Open with Live Server” 即可。

開啟控制檯進行測試:

> myHistory.push('/home')
PUSH /home

url 變為:http://127.0.0.1:5500/home
> myHistory.push('/about')
PUSH /about

url 變成:http://127.0.0.1:5500/about
> myHistory.back()
POP /home

url 變成:http://127.0.0.1:5500/home
> myHistory.replace('about')
REPLACE /about

url 變成:http://127.0.0.1:5500/about
> myHistory.back()
POP /public/test.html

url 變成:http://127.0.0.1:5500/public/test.html

這個流程其實就是 react 路由的基礎。

hash模式:

<script>
    let hashHistory = window.HistoryLibrary.createHashHistory()
    hashHistory.listen(({ location, action }) => {
        console.log(action, location.pathname)
    });
</script>
> hashHistory.push('/home')
PUSH /home

url 變為:http://127.0.0.1:5500/public/test.html#/home

Version 5 is used in React Router version 6 —— history

Tip:react router 用到了這個包,另外這個包的作者和 react-routerreact-router-dom 是同一人。

路由模式

react 中有三種模式,本篇主要研究 history 和 hash 兩種模式。

官網-history:

  • “browser history” - 在特定 DOM 上的實現,使用於支援 HTML5 history API 的 web 瀏覽器中
  • “hash history” - 在特定 DOM 上的實現,使用於舊版本的 web 瀏覽器中
  • “memory history” - 在記憶體中的 history 實現,使用於測試或者非 DOM 環境中,例如 React Native

環境準備

筆者使用的環境是 react 腳手架建立的專案。

Tip:詳細介紹請看 react 腳手架建立專案

開啟 react-router 官網
react-router

Tip:react 有三個版本,我們學習 web 版本 5。印記中文(深入挖掘國外前端新領域,為中國 Web 前端開發...)有 react-router 中文

基礎使用

安裝 react 路由依賴包:

react-cli-demo> npm i react-router-dom@5

added 13 packages, and audited 1421 packages in 6s

169 packages are looking for funding
  run `npm fund` for details

6 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

:版本是5,倘若是版本 6,下面的程式碼執行會報錯。

將 App.js 替換成下面程式碼:

// src/App.js
import React from "react";
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link
} from "react-router-dom";

export default function BasicExample() {
    return (
        <Router>
            <div>
                <ul>
                    <li>
                        <Link to="/">Home</Link>
                    </li>
                    <li>
                        <Link to="/about">About</Link>
                    </li>
                    <li>
                        <Link to="/dashboard">Dashboard</Link>
                    </li>
                </ul>

                <hr />

                <Switch>
                    <Route exact path="/">
                        <Home />
                    </Route>
                    <Route path="/about">
                        <About />
                    </Route>
                    <Route path="/dashboard">
                        <Dashboard />
                    </Route>
                </Switch>
            </div>
        </Router>
    );
}

// Home 元件
function Home() {
    return (
        <div>
            <h2>Home</h2>
        </div>
    );
}

// About 元件
function About() {
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

// Dashboard 元件
function Dashboard() {
    return (
        <div>
            <h2>Dashboard</h2>
        </div>
    );
}

重啟伺服器,頁面顯示:

· Home
· About
· Dashboard
________________________________

Home

:本篇為了演示,所以將多個元件都放在一個檔案中。

整個頁面分上下兩部分,上面是導航區,下面是內容區

倘若點選導航“About”,內容區顯示 About,瀏覽器 url 也會變化:

http://localhost:3000/

變成

http://localhost:3000/about

當我們點選 <Link to="/about">About</Link>,則會匹配上 <Route path="/about">,於是 <About /> 元件顯示。

TipLinkexact<Switch><Router>的作用?請接著看。

<Link to="/about/a">About</Link> 會被渲染成 <a href="/about">About</a>,即使點選 About 導航,渲染的內容依舊不變。

一個特殊版本的 Link,當它與當前 URL 匹配時,為其渲染元素新增樣式屬性 —— 官網-<NavLink>

將 About 導航改成 NavLink

<NavLink to="/about">About</NavLink>

初始時(即未選中)依舊渲染成 <a href="/about">About</a>,但點選 About 導航後,渲染內容變成:

<a href="/about" aria-current="page" class="active">About</a>

所以我們可以給 .active 增加選中效果。

如果要修改預設選中時的 active 類名,可以使用 activeClassName 屬性。就像這樣:

<NavLink to="/about" activeClassName="z-selected">About</NavLink>
  • 版本1
function MyNavLink(props) {
    return <NavLink to={props.to} activeClassName="z-selected">{props.children}</NavLink>
}

使用:

<MyNavLink to="/about" children="About" />
  • 升級版
function MyNavLink(props) {
    return <NavLink {...props} activeClassName="z-selected" />
}

當 React 元素為使用者自定義元件時,它會將 JSX 所接收的屬性(attributes)以及子元件(children)轉換為單個物件傳遞給元件,這個物件被稱之為 “props” —— react 官網

Router

使用 HTML5 歷史 API 記錄( pushState,replaceState 和 popstate 事件)的 <Router> 使您的UI與URL保持同步 —— 官網-BrowserRouter

假如將 BasicExample 元件中的 <Router> 刪除,瀏覽器控制檯將報錯如下:

Uncaught Error: Invariant failed: You should not use <Link> outside a <Router>

未捕獲的錯誤:不變式失敗:您不應該在 <Router> 之外使用 <Link>

倘若將 <Switch> 外邊的 <Router> 刪除,瀏覽器控制檯將報錯如下:

Uncaught Error: Invariant failed: You should not use <Switch> outside a <Router>

未捕獲的錯誤:不變數失敗:您不應該在 <Router> 之外使用 <Switch>

倘若將 <Route><Switch>在這裡可以刪除) 外邊的 <Router> 刪除,瀏覽器控制檯將報錯如下:

Uncaught Error: Invariant failed: You should not use <Route> outside a <Router>

未捕獲的錯誤:不變式失敗:您不應在 <Router> 之外使用 <Route>

倘若我們在 BasicExample 元件中使用兩個 <Router> 會發生什麼?

export default function BasicExample() {
    return (
        <div>
            <Router>
                <ul>
                    <li>
                        <Link to="/">Home</Link>
                    </li>
                    ...
                </ul>
            </Router>
            <hr />

            <Router>
                <Switch>
                    <Route exact path="/">
                        <Home />
                    </Route>
                    ...
                </Switch>
            </Router>
        </div>
    );
}

瀏覽器沒有任何報錯,點選導航“About”,url正常變化,但內容區沒有跟著變。

Router 即路由器,Route 即線路,線路由路由器管理,上面用了兩個路由器,你管你的,我管我的,相互間沒有通訊。

spug_web 中搜尋 <Router 僅出現一次:

// spug_web/src/index.js

ReactDOM.render(
  <Router history={history}>
    <ConfigProvider locale={zhCN} getPopupContainer={() => document.fullscreenElement || document.body}>
      <App/>
    </ConfigProvider>
  </Router>,
  document.getElementById('root')
)

我們也依葫蘆畫瓢,將 <Router> 包裹 <App/>

Switch

js 語法中就有 switch,類似於 if...else。我們對比有無 Switch 的兩種情況:

點選 About 導航,請問內容區顯示什麼?

// 沒有 Switch
export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about">About</Link>
                </li>
                ...
            </ul>
            <hr />

            
            <Route path="/about">
                <About />1
            </Route>
            <Route path="/about">
                <About />2
            </Route>
           ...
        </div>
    );
}

內容區顯示:

About
1
About
2
// 有 Switch
export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about">About</Link>
                </li>
                ...
            </ul>
            <hr />
            <Switch>
                <Route path="/about">
                    <About />1
                </Route>
                <Route path="/about">
                    <About />2
                </Route>
                ...
            </Switch>
        </div>
    );
}

內容區顯示:

About
1

渲染與該地址匹配的第一個子節點 <Route> 或者 <Redirect> —— 官網

Tip:既然只匹配第一個子節點,那麼效能方面肯定會好些,因為不用在嘗試匹配後面的節點。

如果 URL 是 /about ,那麼 <About><User><NoMatch>將全部渲染,因為他們都與路徑匹配:

// from 官網
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>

exact

exact 即精確的。首先做一個小練習:

點選導航 About,下面兩個例子分別輸出什麼,是否匹配?

export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about">About</Link>
                </li>
            </ul>
            <hr />
            <Route path="/about/a">
                <About />
            </Route>
        </div>
    );
}
export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about/a">About</Link>
                </li>
            </ul>
            <hr />
            <Route path="/about">
                <About />
            </Route>
        </div>
    );
}

第一個例子:內容區空白。未能匹配

第二個例子:內容區顯示”About“。匹配。

下面這段程式碼呢?

export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/a/about/b">About</Link>
                </li>
            </ul>
            <hr />
            <Route path="/about">
                <About />
            </Route>
        </div>
    );
}

內容區空白。未能匹配。

總結:Link 可以多給,比如你要 /about,我給你傳 /about/a,但不能少給,而且順序不能亂,例如 /a/about/b 就不能匹配 /about

我們給第二個例子加上 exact,請問輸出什麼?

export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about/a">About</Link>
                </li>
            </ul>
            <hr />
            <Route exact path="/about">
                <About />
            </Route>
        </div>
    );
}

內容區空白。未能匹配。

如果為 true,則只有在路徑完全匹配 location.pathname 時才匹配 —— 官網-exact: bool

:只有需要的時候才開啟精確匹配,也就是說頁面正常,就不要去開啟它。

Redirect

渲染 <Redirect> 將使導航到一個新的地址。這個新的地址會覆蓋 history 棧中的當前地址,類似伺服器端(HTTP 3xx)的重定向 —— 官網-<Redirect>

下面我們用 <Redirect> 解決一個問題:

首先看下面這個例子:

export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about">About</Link>
                </li>
                <li>
                    <Link to="/dashboard">Dashboard</Link>
                </li>
            </ul>
            <hr />
            <Switch>
                <Route path="/about">
                    <About />
                </Route>
                <Route path="/dashboard">
                    <Dashboard />
                </Route>
            </Switch>
        </div>
    );
}

第一次來到網站(http://localhost:3000/),內容區是空白的,因為未能匹配任何 <Route>

現在需求:進入網站,預設顯示 <Dashboard />

只需要增加 2 行程式碼:

import {
  + Redirect,
    ...
} from "react-router-dom";

export default function BasicExample() {
    return (
        <div>
            ...
            <Switch>
                <Route path="/about">
                    <About />
                </Route>
                <Route path="/dashboard">
                    <Dashboard />
                </Route>
              + <Redirect to="/dashboard" />
            </Switch>
        </div>
    );
}

瀏覽器輸入 http://localhost:3000/,由於前兩個 <Route> 未能匹配,最後就會重定向到 http://localhost:3000/dashboard

replace

如果為 true,則單擊連結將替換歷史堆疊中的當前入口,而不是新增新入口 —— 官網-replace: bool

上面 BasicExample 元件,倘若我們依次點選 About 元件、Dashboard 元件,接著點選網頁左上角的返回(<-)按鈕,第一次會返回到 About 元件,再次點選則會返回到 Home 元件。

如果我們給 Dashboard 元件加上 replace 屬性。就像這樣:

<ul>
    <li>
        <Link to="/">Home</Link>
    </li>
    <li>
        <Link to="/about">About</Link>
    </li>
    <li>
        <Link to="/dashboard" replace>Dashboard</Link>
    </li>
</ul>

依次點選 About 元件、Dashboard 元件,然後第一次點選返回按鈕,則回到 Home 元件。

因為點選 Dashboard 導航時,不再是入棧操作(將 /dashboard 壓入棧中),而是替換操作(將棧中的 /about 替換成 /dashboard),再次點選返回,就回到 /。

樣式丟失問題

通過一個示例演示問題:

首先在 index.html 增加樣式:

// public/index.html

+ <link rel="stylesheet" href="./css/index.css" />
// public/css/index.css

body{background-color: pink;}

修改 BasicExample 元件中 About 導航的路徑為多級路徑(/about 非多級路徑;/about/a 多級路徑):

// src/App.js

export default function BasicExample() {
    return (
        <Router>
            <div>
                <ul>
                    <li>
                        <Link to="/">Home</Link>
                    </li>
                    <li>
                        <Link to="/about/a/b">About</Link>
                    </li>
                </ul>

                <hr />

                <Switch>
                    <Route exact path="/">
                        <Home />
                    </Route>
                    <Route path="/about/a/b">
                        <About />
                    </Route>
                </Switch>
            </div>
        </Router>
    );
}

開始測試:

啟動,http://localhost:3000/ 背景是粉色,樣式正常,點選 ”About“導航,url 變成 http://localhost:3000/about/a/b,背景依舊是粉色,重新整理頁面,粉色背景不見了,也就是樣式丟失了!

重新整理的時候,發現樣式請求的地址和返回內容如下:

http://localhost:3000/about/a/css/index.css
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="/logo192.png" />
  <link rel="stylesheet" href="./css/index.css" />
  <link rel="manifest" href="/manifest.json" />
  <title>React App</title>
<script defer src="/static/js/bundle.js"></script></head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>

</html>

:請求不存在的資源,伺服器會將 public/index.html 返回給你。例如請求:http://localhost:3000/a/b/c/d

我們放慢重新整理這個動作:

  1. 重新整理,給伺服器傳送 http://localhost:3000/about/a/b,沒有這個資源,所以伺服器返回 index.html。
  2. 瀏覽器解析 index.html,遇到 <link rel="stylesheet" href="./css/index.css" />,需要載入當前目錄下的 css/index.css 資源,當前目錄是 http://localhost:3000/about/a,於是請求 http://localhost:3000/about/a/css/index.css
  3. 由於存在對應資源,伺服器再次返回 index.html,樣式也就丟失了

既然知道問題原因,只需要讓 css 資源路徑正常即可:

將

<link rel="stylesheet" href="./css/index.css" />

改成

<link rel="stylesheet" href="/css/index.css" />

或 

<link rel="stylesheet" href="%PUBLIC_URL%/css/index.css" />

巢狀路由

巢狀路由也叫子路由

將 BasicExample 元件中的 About 改造成巢狀路由。

首先看效果:

初始時 Home 導航選中:

· Home
· About
· Dashboard
________________________________

Home

點選 About 導航:

· Home
· About
· Dashboard
________________________________

About
________________________________

· article1
· article2

點選 article2 導航,顯示:

· Home
· About
· Dashboard
________________________________

About
________________________________

· article1
· article2

文章2...

巢狀路由相關程式碼如下:

// About 元件
function About() {
    return (
        // 新增一個路由器 Router
        <Router>
            <div>
                <h2>About</h2>
                <hr />
                <ul>
                    <li>
                        <Link to="/about/article1">article1</Link>
                    </li>
                    <li>
                        <Link to="/about/article2">article2</Link>
                    </li>

                </ul>
                <Switch>
                    <Route path="/about/article1">
                        文章1...
                    </Route>
                    <Route path="/about/article2">
                        文章2...
                    </Route>
                </Switch>
            </div>
        </Router>
    );
}

路由元件 vs 一般元件

路由元件和一般元件最大的一個區別是:props 中是否有路由相關方法。

這裡有三個元件,請觀察每個元件的 props

// src/App.js

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

export default function BasicExample() {
    return (
        <div>
            <Header />
            <ul>
                <li>
                    <Link to="/about">About</Link>
                </li>
                <li>
                    <Link to="/dashboard">Dashboard</Link>
                </li>
            </ul>
            <hr />
            <Switch>
                <Route path="/about" component={About} />
                <Route path="/dashboard">
                    <Dashboard name="pjl" />
                </Route>
            </Switch>
        </div>
    );
}

// Home 元件
function Header(props) {
    console.log('Header props,', props)
    return (
        <h2>Header</h2>
    );
}

// About 元件
function About(props) {
    console.log('About props,', props)
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

// Dashboard 元件
function Dashboard(props) {
    console.log('Dashboard props,', props)
    return (
        <div>
            <h2>Dashboard</h2>
        </div>
    );
}

初始 url 為:http://localhost:3000/,控制檯輸出:Header props, {}

點選 Dashboard 導航,控制檯輸出:Dashboard props, {name: 'pjl'}

點選 About 導航,控制檯輸出:

About props, {history: {…}, location: {…}, match: {…}, staticContext: undefined}

三個元件只有 About 元件是路由元件,其用法不同於另外兩種元件:

// 通過 component 屬性指定元件
<Route path="/about" component={About} />
<Dashboard name="pjl" />

<Header />

Tip:路由元件中的 history、location、match 屬性,下文都會講到。

pages/components 目錄

有人說路由元件和一般元件從專案結構上可以區分,比如將路由元件放在 src/pages 資料夾中,一般元件放在 src/components 中。

spug_web 中有 src/pagessrc/components 目錄,是否就是根據一般元件和路由元件進行區分?請看擷取的程式碼片段:

首先是 src/routes.js,猜測與路由相關:

// src/routes.js

import HomeIndex from './pages/home';
import DashboardIndex from './pages/dashboard';
import HostIndex from './pages/host';
import ExecTask from './pages/exec/task';
import ExecTemplate from './pages/exec/template';
import DeployApp from './pages/deploy/app';
import DeployRepository from './pages/deploy/repository';
import DeployRequest from './pages/deploy/request';
import ScheduleIndex from './pages/schedule';
import ConfigEnvironment from './pages/config/environment';
import ConfigService from './pages/config/service';
import ConfigApp from './pages/config/app';
import ConfigSetting from './pages/config/setting';
import MonitorIndex from './pages/monitor';
import AlarmIndex from './pages/alarm/alarm';
import AlarmGroup from './pages/alarm/group';
import AlarmContact from './pages/alarm/contact';
import SystemAccount from './pages/system/account';
import SystemRole from './pages/system/role';
import SystemSetting from './pages/system/setting';
import WelcomeIndex from './pages/welcome/index';
import WelcomeInfo from './pages/welcome/info';

引入的都是 pages 中的元件。

src/routes.js 又被 src\layout\index.js 引用:

// src/layout/index.js

import routes from '../routes';

// initRoutes 的實參 routes 就是上面匯入的 routes
function initRoutes(Routes, routes) {
  for (let route of routes) {
    if (route.component) {
      if (!route.auth || hasPermission(route.auth)) {
        // 通過 component 屬性指定元件
        Routes.push(<Route exact key={route.path} path={route.path} component={route.component}/>)
      }
    } else if (route.child) {
      initRoutes(Routes, route.child)
    }
  }
}
...

至此,初步判斷spug_web 中的 pages 目錄和 components 目錄就是根據一般元件和路由元件進行區分的。

給路由元件傳遞引數

給路由元件傳遞引數有三種方式。

下面通過這三種方式實現同一個功能:給路由元件 About 傳遞 nameage 兩個引數。

params 方式

export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about/pjl/18">About</Link>
                </li>
            </ul>
            <hr />
            <Switch>
                <Route path="/about/:name/:age" component={About}>
                </Route>
            </Switch>
        </div>
    );
}

// About 元件
function About(props) {
    // {history: {…}, location: {…}, match: {…}, staticContext: undefined}
    console.log(props)
    // {name: 'pjl', age: '18'}
    console.log(props.match.params)
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

點選 About 導航元件,控制檯輸出:

{history: {…}, location: {…}, match: {…}, staticContext: undefined}

// 接收兩個引數
{name: 'pjl', age: '18'}

一個 match 物件中包涵了有關如何匹配 URL 的資訊 —— 官網-match

search 方式

以 search 方式重寫 params 傳遞引數的例子:

export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about/?name=pjl&age=18">About</Link>
                </li>
            </ul>
            <hr />
            <Switch>
                <Route path="/about" component={About}>
                </Route>
            </Switch>
        </div>
    );
}

// About 元件
function About(props) {
    // ?name=pjl&age=18
    console.log(props.location.search)
    var searchParams = new URLSearchParams(props.location.search)
    const params = {}
    for (const [key, value] of searchParams) {
        params[key] = value
    }
    // params:  {name: 'pjl', age: '18'}
    console.log('params: ', params);
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

需要自己將接收到的資料(例如 ?name=pjl&age=18)處理一下。

Tipparamssearch 傳參,在位址列中都能看見。例如 search:http://localhost:3000/about/?name=pjl&age=18。重新整理頁面引數都不會丟失。

state 方式

:與元件中的 state 沒有任何關係

export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to={{ pathname: '/about', state: { name: 'pjl', age: 18 } }}>About</Link>
                </li>
            </ul>
            <hr />
            <Switch>
                <Route path="/about" component={About}>
                </Route>
            </Switch>
        </div>
    );
}

// About 元件
function About(props) {
    // {name: 'pjl', age: 18}
    console.log(props.location.state)
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

有兩個特點:

  • 所傳引數不會再 url 中體現。比如這裡仍然是 http://localhost:3000/about
  • 強制重新整理 url,所傳引數也不會消失。
    • 筆者嘗試關閉瀏覽器,再次輸出 http://localhost:3000/about,控制檯輸出 undefined

Tipprops.location === props.history.locationtrue

HashRouter 重新整理會導致 state 引數丟失

HashRouter 模式下,重新整理(非強刷)頁面會造成 state 引數的丟失。

將 App.js 中的 BrowserRouter 切換成 HashRouter 進行自測即可。

程式設計式導航 history

比如過3秒需要自動跳轉,這時就可以使用程式設計式導航。用法類似 History API。不過這裡我們操作的是 props.history

執行下面這個熟悉的例子,將會把 props.history 匯出給 window.aHistory,我們直接在控制檯中操作 aHistory

// src/App.js
export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/">Home</Link>
                </li>
                <li>
                    <Link to="/about">About</Link>
                </li>
                <li>
                    <Link to="/dashboard">Dashboard</Link>
                </li>
            </ul>
            <hr />
            <Switch>
                <Route exact path="/" component={Home} />

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

                <Route path="/dashboard" component={Dashboard} />
            </Switch>
        </div>
    );
}

// Home 元件
function Home(props) {
    // 將 history 匯出,用於測試
    window.aHistory = props.history
    return (
        <div>
            <h2>Home</h2>
        </div>
    );
}

// About 元件
function About() {
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

// Dashboard 元件
function Dashboard() {
    return (
        <div>
            <h2>Dashboard</h2>
        </div>
    );
}

瀏覽器 url 是 http://localhost:3000/,頁面內容如下:

· Home
· About
· Dashboard
________________________________

Home

測試開始:

> aHistory.push('/about')

url:http://localhost:3000/about
> aHistory.push('/dashboard')

url: http://localhost:3000/dashboard
> aHistory.goBack()

url: http://localhost:3000/about
// 等於 aHistory.goBack()
> aHistory.go(-1)

url: http://localhost:3000/

一般元件中使用程式設計式導航

一般元件中沒有 history,如果需要使用程式設計式導航,可以藉助 withRouter 將一般元件處理一下即可。請看示例:

import React from "react";
import {
    Switch,
    Route,
    withRouter,
    Link
} from "react-router-dom";

export default function BasicExample() {
    return (
        <div>
            <ul>
                <li>
                    <Link to="/about">About</Link>
                </li>
                <li>
                    <Link to="/new-about">NewAbout</Link>
                </li>
            </ul>
            <hr />
            <Switch>
                <Route path="/about">
                    <About />
                </Route>
                <Route path="/new-about">
                    <NewAbout />
                </Route>

            </Switch>
        </div>
    );
}

// About 元件
function About(props) {
    console.log(props)
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

var NewAbout = withRouter(About)

頁面顯示:

About
NewAbout
________________________________

依次點選 About 導航、NewAbout 導航,控制檯輸出:

{}

{history: {…}, location: {…}, match: {…}, staticContext: undefined}

其他章節請看:

七天接手react專案 系列

相關文章