Apps Script用Sheet生成動態網頁(18): 使用router建構選單功能

前幾篇已經先將基本的React語法及Hook概念說明了一下,這裡要開始以React建構之前的功能,如果對於之前的React基礎還不太明瞭的部份,可以在底下請留言,或從React官方網站中學習一下基礎,對於之後的部份才會能順利銜結。

之前我們建構的Apps Script網站有幾項功能,登入會員、顯示Drive資料夾、上傳HTML、顯示/刪除已上傳的HTML等等。現在用React就要做一個整理,建構一個在網站上方的選單功能,可以方便使用者點擊進入不同的功能。

1. 產生各功能頁面

為了避免講解太過複雜,我們挑出3個基本的講解,分別是:
  1. 首頁
  2. Drive檔案列表
  3. 登入
首頁部份就沿用範本原本的首頁;後兩者我們先以簡單頁面來呈現,另外我們還會建構一個找不到對應功能時,顯示走錯了的頁面,這裡說起來會有四個頁面。習慣上,React頁面會放在pages目錄,而其他的元件會放在components目錄裡頭。現在建構的頁面我們就在pages目錄裡頭放置Home.jsx, DriveLister.jsx, Login.jsx, 以及 NoSuchPage.jsx。

Home.jsx就將原本App.jsx的內容做拷貝,然後將const App = () => { 及最後export default App; 改成 const Home = () => { 及 export default Home;。對了還要移除import '../styles.css';部份,這個引用仍會保留在App.jsx中。

DriveLister.jsx和Login.jsx則是一個簡單頁面,後面幾篇我們會再添加功能。

DriveLister.jsx
import React from 'react';
const DirveLister = () => {
return <div>Google Drive檔案列表</div>;
};
export default DirveLister;

Login.jsx
import React from 'react';
const Login = () => {
return <div>登入</div>;
};
export default Login;

找不到頁面裡頭則是放置了一個連結,讓使用者可以回首頁。

找不到頁面
import React from 'react';
import { Link } from 'react-router-dom';
const NoSuchPage = () => {
return (
<div>
找不到頁面,
<Link to="/home">按此回首頁</Link>
</div>
);
};
export default NoSuchPage;


這裡使用的是react-router-dom套件的Link功能,而不是使用<a href="/home">。是因為這是我們網頁中的連結,不是真實的/home連結。在這裡如果直接使用/home超連結,是會連結不到的。

2. 建構網站內連結

就像我上面提到的,我們接著要建構網站內的連結,建構的方式有兩種,一種是透過Apps Script的網址來,但是在React的網站中,大多數的網站內連結都不需要透過Apps Script網站提供網頁,而是以javascript來請瀏覽器所繪製的畫面,這類的畫面我們都會透過router套件來作頁面的切換,接著就開始建構React內的連結吧!

安裝router套件

在原始的範本中並沒有先安裝router套件,是因為它並沒有需要建構可互動的動態網站的需求。但是我們有,所以這裡要先安裝react-router-dom這個router套件。

yarn add react-router-dom react-router-bootstrap
yarn add -D @types/react-router-bootstrap @types/react-router-dom

在react網頁部份是安裝react-router-dom這個知名套件,以及為了方便route與ReactBootstrap一起使用的 react-router-bootstrap套件。

第二行安裝的@types部份是針對開發使用,可以讓VSCode編輯器在使用到router套件功能時中顯示參數提示。

引用router套件

前面已經安裝好router套件,接著就是要建構網站內連結,這裡使用的Router是MemoryRouter,因為常用的BrowserRouter和HashRouter,當我們上傳到Apps Script中使用時,都有發生一些問題[註1],所以我們這裡只好選擇MemoryRouter(雖然還是有1個問題[註2])。下面列出程式碼,可以看到我們引用了4個頁面。

import React from 'react';
import { MemoryRouter as Router, Switch, Route } from 'react-router-dom';

import Home from '../Pages/Home';
import DirveLister from '../Pages/DirveLister';
import Login from '../Pages/Login';
import NoSuchPage from '../Pages/NoSuchPage';

import MyNav from './MyNav';

import '../styles.css';

const App = () => {
return (
<Router>
<MyNav />
<Switch>
<Route path={['/', '/home']}><Home /></Route>
<Route path="/drive-lister"><DirveLister /></Route>
<Route path="/login"><Login /></Route>
<Route><NoSuchPage /></Route>
</Switch>
</Router>
);
};
export default App;

在最外頭使用Router元件,內部包裹Switch,再來各個網頁連結則是透過Route元件來定義。

Route使用path定義,這裡以/開頭(題外話,如果是HashRouter是以#開頭),裡頭包裹著各個頁面。最後可以看到Route在NoSuchPage部份並沒有寫path,這就是所謂的預設route,當網站內連結寫錯時,或是沒有定義時,都會被router導向這個預設route。

在首頁部份你會看到path={['/', '/home']},這是部份是說/和/home都可以跑到這個頁面,後面的寫法是因為JSX與js互動的方法需要先寫一層大括號{},它其實只是要給予這個屬性一個js陣列而已。

這樣我們就建構了四個內部的連結規則,也讓我們的NoSuchPage裡頭的/home連結可以使用了!

3. 建構選單界面

在前面我們建構了各個頁面和連結規則,接著就建構選單。程式碼如下:

import React from 'react';
import { Navbar, Nav, Container } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

const MyNav = () => {
return (
<Navbar bg="primary" variant="dark" collapseOnSelect expand="md">
<Container>
<Navbar.Brand>React on AppScript</Navbar.Brand>
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="me-auto">
<LinkContainer to="/home">
<Nav.Link>首頁</Nav.Link>
</LinkContainer>
<LinkContainer to="/drive-lister">
<Nav.Link>Drive檔案列表</Nav.Link>
</LinkContainer>
</Nav>
<Nav>
<LinkContainer to="/login">
<Nav.Link>登入</Nav.Link>
</LinkContainer>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
);
};
export default MyNav;

這裡參考ReactBootstrap的範例,為了使用router功能再加入react-router-bootstrap的LinkContainer功能指定連結到哪個route上去。

在這段程式碼中比較不太了解的部份應該是下列三者:
  1. Navbar的variant="dark",這是因為他要讓前景色變成白的,所以會表示它是深色,如果你沒有指定的話,預設前景會使用黑色,導致選單上的字都是黑色,與藍色的背景不是很搭。
  2. Navbar的collapseOnSelect expand="md",這是用來設定選單何時要收合,變成一個漢堡的圖示。漢堡的圖示就是透過Navbar.Toggle來指定可以互動的元件。而Navbar.Collapse部份則是收合的內容,這裡我們的設置就是讓選單內容都會被收合,而網站標題Navbar.Brand不會被收合。
  3. Nav的className="me-auto",這個是用來表示元件右邊要填充邊界,可以網站主功能置左,而登入按鈕置右。

4. 實作結果呈現



註1: 使用BrowserRouter有兩個問題(1)Apps Script首頁的路徑是/exec結尾,會在URL加上頁面路徑中取代/exec路徑,導致沒辦法正常呼叫到doGet() (2)在沒有登入google帳號的狀況下,你使用Browser上的URL進入網站,會顯示Google登入頁面;使用HashRouter部份,則是點擊連結完全不會換頁,我想應該是AppsScript因為有用一層iframe隔絕我們的React網頁取得當前或改變網站當下的hash,所以沒有辦法切換頁面。
註2: 使用MemoryRouter的問題就是你重新進入網頁或刷新網頁,都只會進入首頁。



留言