iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 16
1
Modern Web

從比入門再往前一點開始,一直到深入React.js系列 第 16

【Day.16】React入門 - 想要分頁? react-router-dom

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

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

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

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

React-router-dom就是在React達成前端路由的插件之一。他是基於React-router這個核心製作,衍伸的家族還有在react-native使用的react-router-native。

前置作業 - 製作分頁

這裡我們的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,Route,Switch,Link} from "react-router-dom";

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

import {HashRouter,Route,Switch} 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 ReactDOM from 'react-dom';

import {HashRouter,Route,Switch} from "react-router-dom";
import MenuPage from "./page/MenuPage";
import FormPage from "./page/FormPage";

const App = () =>{
    return( 
        <HashRouter>

        </HashRouter>
    );
}

ReactDOM.render(    
    <App/>,
    document.getElementById('root')
);

Switch

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

import React from 'react';
import ReactDOM from 'react-dom';

import {HashRouter,Route,Switch} from "react-router-dom";
import MenuPage from "./page/MenuPage";
import FormPage from "./page/FormPage";

const App = () =>{
    return( 
        <HashRouter>
            <Switch>
            
            </Switch>
        </HashRouter>
    );
}

ReactDOM.render(    
    <App/>,
    document.getElementById('root')
);

Route

Route就是用來設定分頁的元件,用path這個props來設定url字串,它的使用方法有兩種:

  • 第一種方式,FormPage會被轉為React.creactElement
<Route path="/form" component={FormPage}/>
  • 第二種方式,用函式回傳React元件
<Route path="/form" render={()=>{return( <FormPage/> )}}/>

平常會用第一種方式,但如果你想要在分頁元件上綁props,就要用第二種。

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

import React from 'react';
import ReactDOM from 'react-dom';

import {HashRouter,Route,Switch} from "react-router-dom";
import MenuPage from "./page/MenuPage";
import FormPage from "./page/FormPage";

const App = () =>{
    return( 
        <HashRouter>
            <Switch>
                    <Route exact={true} path="/" component={MenuPage}/>
                    <Route path="/form" component={FormPage}/>
            </Switch>
        </HashRouter>
    );
}

ReactDOM.render(    
    <App/>,
    document.getElementById('root')
);

為什麼這裡<Route exact={true} path="/" component={MenuPage}/>要加上exact={true}呢? 這是因為path="/form"當中也有包含/。React router dom在檢查路由時是依照順序的,如果今天用戶拜訪了path="/form",React router dom會在檢查<Route path="/" component={MenuPage}/>時就認定包含/路由,所以顯示MenuPage

exact這個props就是用來限定路由一定要完全跟path一模一樣才顯示。因為是布林值,你也可以只寫名字不給值。

這樣就完成了分頁。

固定Layout

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

import React from 'react';
import ReactDOM from 'react-dom';

import {HashRouter,Route,Switch} 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>
            <Switch>
                <Layout>
                    <Route exact path="/" component={MenuPage}/>
                    <Route path="/form" component={FormPage}/>
                </Layout>
            </Switch>
        </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 ReactDOM from 'react-dom';

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 }
        </>
    )
}

所有的程式碼:

import React from 'react';
import ReactDOM from 'react-dom';

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>
            <Switch>
                <Layout>
                    <Route exact path="/" component={MenuPage}/>
                    <Route path="/form" component={FormPage}/>
                </Layout>
            </Switch>
        </HashRouter>
    );
}

ReactDOM.render(    
    <App/>,
    document.getElementById('root')
);

執行結果:

Link還能透過location api傳資料。詳請請參考官方文件

CSR的衍伸問題

CSR延伸的問題是因為程式碼都用JS處理,導致SEO的時候只會抓到原本那個空的<div id="root"></div>。為了解決這個問題,衍伸出了在後端製作React網頁(SSR)的方法。我們最後面會回頭來講這個


上一篇
【Day.15】React入門 - 非控制組件與useRef
下一篇
【Day.17】React入門 - 利用useContext進行多層component溝通
系列文
從比入門再往前一點開始,一直到深入React.js30

尚未有邦友留言

立即登入留言