React.js
React基礎
React router DOM

在過去,當我們要製作「分頁」時,多半是新增一個靜態 HTML 檔,讓 web server 根據檔案路徑去尋找,或是透過後端程式碼去定義什麼 url 要對應到哪個 HTML 檔。這種方式我們稱為伺服器渲染(SSR)

然而這卻也產生了一個問題。

即使頁面中大多是固定的 Layout,但換頁的時候,因為是拜訪新檔案,整個頁面都要刷新。

為了解決這個問題,工程師決定也用 Javascript 從去創造前端路由控制器。換頁的時候,只用 JS 去改變不一樣的地方。這樣的網頁程式換頁時不需要整頁都刷新,使用起來跟 APP 很像,因此又稱為 Single Page Application(SPA,單頁式網頁應用)。也因為大多改在瀏覽器製造頁面,這樣的方式也稱為客戶端渲染(CSR)。

React-router-dom 就是在 React 達成前端路由的插件之一。

前置作業 - 製作分頁

這裡我們的Menu.jsMenuItem.js 會依照 Day.12 的程式,InputForm.js會依照 Day.15 的程式。然後我們要新增 src/page 資料夾,並在裡面製作兩個用來當作分頁的頁面:

  • src/page/MenuPage.js
import React from 'react';
 
import MenuItem from '../component/MenuItem';
import Menu from '../component/Menu';
 
let menuItemWording = ['Like的發問', 'Like的回答', 'Like的文章', 'Like的留言'];
 
const MenuPage = () => {
    let menuItemArr = menuItemWording.map(wording => <MenuItem text={wording} />);
 
    return <Menu title={'Andy Chang的like'}>{menuItemArr}</Menu>;
};
 
export default MenuPage;
  • src/page/FormPage.js
import React from 'react';
import InputForm from '../component/InputForm';
 
const FormPage = () => {
    return <InputForm />;
};
 
export default FormPage;

接下來,我們會嘗試在 src/index.js 來控制並創造控制分頁的路由。

環境設定

請打開 terminal,並輸入

 npm i react-router-dom --save

安裝完畢後,進入 src/index.js,在開頭引入

import React from 'react';
import ReactDOM from 'react-dom';
 
import MenuPage from './page/MenuPage';
import FormPage from './page/FormPage';
 
import { HashRouter, Routes, Route, Link } from 'react-router-dom';

其中這一行會是所有我們要用到的元件

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

HashRouter

路由器的英文是 Router,但為甚麼這裡要加一個 Hash 呢? 這是因為如果我們要從前端去判斷當前的 url 是什麼,必須要在根路徑最後方加入一個#。JS 才能從#後方的字串去判斷。

當然,React-router-dom 也有提供不會有#BrowserRouter。但這個會需要後端的配合,我們目前只有純前端檔案就先用 HashRouter。

現在,請在 src/index.js 創造一個元件App,讓 React 程式統一從這個元件渲染。並在裡面先加入<HashRouter></HashRouter>

import React from 'react';
import { createRoot } from 'react-dom/client';
 
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import MenuPage from './page/MenuPage';
import FormPage from './page/FormPage';
 
const App = () => {
    return <HashRouter></HashRouter>;
};
 
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Routes

Routes 這個元件是用來正確地判斷路由應該對應到誰。我們一樣在 src/index.js 裡面加入<Routes></Routes>

import React from 'react';
import { createRoot } from 'react-dom/client';
 
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import MenuPage from './page/MenuPage';
import FormPage from './page/FormPage';
 
const App = () => {
    return (
        <HashRouter>
            <Routes></Routes>
        </HashRouter>
    );
};
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Route

Route 就是用來設定分頁的元件,用path這個 props 來設定 url 字串:

<Route path="/form" element={<FormPage/>} />

現在,把我們的分頁元件用 Route 加進 App 中:

import React from 'react';
import { createRoot } from 'react-dom/client';
 
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import MenuPage from './page/MenuPage';
import FormPage from './page/FormPage';
 
const App = () => {
    return (
        <HashRouter>
            <Routes>
                <Route path="/" element={<MenuPage/>} />
                <Route path="/form" element={<FormPage/>} />
            </Routes>
        </HashRouter>
    );
};
const root = createRoot(document.getElementById('root'));
root.render(<App />);

這樣就完成了分頁。

固定 Layout

然而這樣並沒有顯示出 SPA 的感覺。所以現在我們來做一個固定的導覽列。請在 src/index.js 上方新增一個 Layout 元件:

import React from 'react';
import { createRoot } from 'react-dom/client';
 
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import MenuPage from "./page/MenuPage";
import FormPage from "./page/FormPage";
 
const Layout = () => {
    return(
 
    )
}
 
const App = () =>{
/* 省略 */

然後在 App 中使用 Layout 把所有 Route 包起來:

const App = () => {
    return (
        <HashRouter>
            <Routes>
                <Layout>
                    <Route path="/" element={<MenuPage/>} />
                    <Route path="/form" element={<FormPage/>} />
                </Layout>
            </Routes>
        </HashRouter>
    );
};

然後我們就能在 Layout 中以 props.children 來顯示對應的 Route

const Layout = props => {
    return <>{props.children}</>;
};

但是目前我們還缺少了前往分頁的導覽列,請在 Layout 中新增<nav>

const Layout = props => {
    return (
        <>
            <nav></nav>
            {props.children}
        </>
    );
};

接下來就是要加入超連結了。

Link

一般講到超連結,我們會聯想到<a href="/路徑">,但這裡我們要使用的是 React-router-dom 提供的原件<Link>

為什麼要特別多弄一個元件呢?

這是因為<a href="/路徑">是預設導向主domain/路徑,當我們今天使用的是 subdomain 或是像 hash router 這種東西時,就要自己把 subdomain 或是#補進去,像是<a href="/#/路徑">。這樣當我們今天專案部屬環境不同時就很麻煩。

Link 這個元件就會方便我們導向/統一管理要導向的路徑。它的語法是

<Link to="路徑">

現在,我們在開頭引入 Link 這個元素,並使用在<nav></nav>中。

import React from 'react';
import { createRoot } from 'react-dom/client';
 
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import MenuPage from './page/MenuPage';
import FormPage from './page/FormPage';
 
const Layout = props => {
    return (
        <>
            <nav>
                <Link to="/">點我連到第一頁</Link>
                <Link to="/form" style={{ marginLeft: '20px' }}>
                    點我連到第二頁
                </Link>
            </nav>
            {props.children}
        </>
    );
};

所有的程式碼:

import React from 'react';
import { createRoot } from 'react-dom/client';
 
import { HashRouter, Route, Switch, Link } from 'react-router-dom';
import MenuPage from './page/MenuPage';
import FormPage from './page/FormPage';
 
const Layout = props => {
    return (
        <>
            <nav>
                <Link to="/">點我連到第一頁</Link>
                <Link to="/form" style={{ marginLeft: '20px' }}>
                    點我連到第二頁
                </Link>
            </nav>
            {props.children}
        </>
    );
};
 
const App = () => {
    return (
        <HashRouter>
            <Routes>
                <Layout>
                    <Route path="/" element={<MenuPage/>} />
                    <Route path="/form" element={<FormPage/>} />
                </Layout>
            </Routes>
        </HashRouter>
    );
};
 
const root = createRoot(document.getElementById('root'));
root.render(<App />);

執行結果:

Link 還能透過 location api 傳資料。詳請請參考官方文件 (opens in a new tab)