經過了兩天廢話,我們終於要開始寫程式啦~ ✧。* ٩(ˊᗜˋ)و✧。**
Next.js 的首頁內容由 src/app/page.tsx
決定
const Page = () => (
<div className="flex min-h-screen items-center justify-center ">
Hello World
</div>
)
export default Page
用以上程式碼取代原本的 page.tsx
,就能看到原本的 Next.js 頁面變成置中的 Hello World。
上面 flex min-h-screen items-center justify-center
這串文字其實就是 tailwind 設定。每個空格分隔的字串都對應到一個 css 屬性,例如 flex
對應到 css 的 display: flex
,而items-center
則對應到 align-items:center
,這些對應關係在 Tailwind 文件 中都找的到。
網頁的外框,由 src/app/layout.tsx
決定
export const metadata: Metadata = {
title: 'Course Management System', //before: Create Next App
description: 'Generated by create next app',
}
當我們把它的 title 改掉,就能看到瀏覽器頁籤發生變化
當然 icon 也是可以換的,只要把 favicon.ico
換成自己喜歡的就可以了。
以上程式碼可以參考 D03/hello-world
我們的課表預計會有四個頁面
路由 | 用途 |
---|---|
/edit/courses/all/page.tsx | 新增課程/刪除課程/新增課程參與者 |
/edit/courses/[course]/weeks/[week]/page.tsx | 編輯當週課表 |
/view/courses/all/page.tsx | 瀏覽所有有參與的課程 |
/view/courses/[course]/weeks/[week]/page.tsx | 瀏覽當週課表 |
const Page = () => (
<div className="flex min-h-screen items-center justify-center ">
edit courses
</div>
)
export default Page
Next.js 藉由資料夾路徑來自動產生路由,我們只要把以上程式碼複製到 src/app/edit/courses/page.tsx
,就可以在 http://localhost:3000/edit/courses/page.tsx 看到以下畫面。
無論是表格、標題列或是表單,我們都可以在 flowbite 找到對應的 Tailwind CSS 範本。使用這些範本可以省下大量手動設定元件外觀的時間,同時保留客製化的可能性。
開發過程我們會新增大量的元件,以下擴充功能可以藉由關鍵字快速生成常用 react 程式碼 Snippets,省下重複打字時間。
例如以下程式碼使用關鍵字 rface
就可以瞬間產生
import React from 'react'
const Page = () => {
return (
<div>Page</div>
)
}
export default Page
有了 snippets 產生的外框,我們還要在加一點外觀設定上去。再次提醒這些對應關係都可以在 tailwind 文件裡面查到 。
import React from 'react'
const Page = () => {
return (
// 1. 使用 tailwind 的設定讓元件垂直水平置中
<div className="flex min-h-screen items-center justify-center ">
{/*2. 寬度 600*/}
<div className="w-[600px]">
{/*3. 貼上 flowbite HTML */}
</div>
</div>
)
}
export default Page
接下來我們從 flowbite textarea 複製程式碼到 步驟3 的位置。在複製後有紅色 highlight 很正常,這是因為我們寫的並不是真正的 HTML,而是 JSX / TSX 語法。
<label
// for="message" // html
htmlFor="" // jsx
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
<textarea
id="message"
// rows="4" // html
rows={4} // jsx
//...
></textarea>
覺得手動替換麻煩的話也可以利用擴充功能來轉換。
我的系統是預設開啟暗黑模式的,因此會看到畫面上顯示的也是暗黑模式的元件。如果要手動控制暗黑模式,我們可以進行以下設定。
/tailwind.config.ts 新增 darkMode 設定
const config: Config ={
// ...
darkMode: 'class'
// ...
}
export default config
src/app/layout.ts 新增 className='light'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className='light'>
<body className={inter.className}>{children}</body>
</html>
)
}
設定完變得怪怪的,這是因為 /src/app/global.css 裡面 Next.js 的設定是基於 media 而不是 class。不過不用擔心我們只要進行以下變更,就能基於 class 決定是否開啟暗黑模式。
before
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
after
.dark {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
畫面
我們以 Flowbite DatePicker 為例,它包含了日期選擇的邏輯,這時候光靠 HTML + CSS 是不夠的,需要 JS 介入處理,所以我們需要執行以下指令安裝 JS 依賴。
npm i flowbite
npm i @richard-wang-tw/flowbite-datepicker
# 原本的指令是 npm i flowbite-datepicker
# 但是它和 next.js 13 app router 不相容
# 所以我上了一個修好這些問題的版本
然後調整 tailwind.config.ts 設定
const config: Config = {
//...
content: [
'./node_modules/flowbite/**/*.js',
// ...
],
//...
plugins: [require('flowbite/plugin')],
//...
}
最後參考以下步驟包裝成一個新的 TSX元件
// 使用 JSX 是因為 flowbite-datepicker 不支援 TS
'use client' // 1. 指定這個元件是 client元件,這樣才可以使用 useEffect / useRef
import { DateRangePicker as FlowbiteDateRangePicker } from '@richard-wang-tw/flowbite-datepicker'
import { useEffect, useRef } from 'react'
export const DateRangePicker = () => {
// 2. 建立一個 ref 來儲存 picker element
const pickerRef = useRef<HTMLDivElement>(null)
// 3. 每次 rernder 時都執行一次 DatePicker 元件相關的 JS
// 因為牽涉到各種 dom 操作是 side effect
// 所以要包在 useEffect 裡面使用
useEffect(() => {
new FlowbiteDateRangePicker(pickerRef.current)
})
return (
{/* 4. 複製下方 date range picker 連結中的程式碼到這裡貼上
5. 轉換成 JSX 格式
6. 新增 ref={pickerRef}
7. 移除不必要且無法使用的 date-rangepicker*/}
<div ref={pickerRef} className="flex items-center">
{/* 省略 */}
</div>
)
}
切換到分支 D03/static-pages,執行以上指令後透過表格連結,即可看到範例網站。
npm i
npm run dev
連結 | 用途 |
---|---|
/edit/courses/all/page.tsx | 新增課程/刪除課程/新增課程參與者 |
/edit/courses/[course]/weeks/[week]/page.tsx | 編輯當週課表 |
/view/courses/all/page.tsx | 瀏覽所有有參與的課程 |
/view/courses/[course]/weeks/[week]/page.tsx | 瀏覽當週課表 |
範例網站預設開啟白天模式,可以前往 src/app/layout.tsx 切換為暗黑模式
// before
<html lang="en" className="light">
// after
<html lang="en" className="dark">
學會複製貼上元件後,很快就可以組出看起來還不錯的網頁。但隨著網頁複雜度增加,程式碼一個不注意就會往雜亂無章的深淵跌落,造成本來很開心寫網頁的心情漸漸煩躁起來。如果要避免這種情形就勢必要把通用的部份抽出來,然後依照資料夾分門別類的整理好。可是怎麼做呢? 好多元件、好多要注意的細節,如何讓人一目了然 ?
以下是我的一些習慣分享給大家
元件超過 300 行就開始考慮拆分
如果檔案還不大,可以把小元件放在同個檔案裡
onst ComponentA1 = ()=>(<xxx>{...}</xxx>)
onst ComponentA2 = ()=>(<xxx>{...}</xxx>)
onst ComponentA = ()=>(
<xxx>
<ComponentA1/>
<ComponentA2/>
</xxx>
xport default ComponentA
``
如果檔案已經超過 300 行,考慮拆成不同檔案
Before
omponentA.tsx
After
ComponentA
index.tsx (ComponentA)
ComponentA1.tsx
ComponentA2.tsx
``
No abstraction is better than wrong abstraction
審慎的思考要怎麼拆比較好,如果還沒想到怎麼拆不如先不要拆
抽取出來的元件越小,越方便重複使用
以 tailwind 來說,通用的元件最好提供 className props,搭配 twMerge 可以方便後續微調
import { FC } from 'react'
import { twMerge } from 'tailwind-merge'
const CLASS_NAMES = {
primary: 'tailwind class names',
light: 'tailwind class names',
} as const
interface ButtonProps extends React.PropsWithChildren {
className?: string
type?: keyof typeof CLASS_NAMES
}
export const Button: FC<ButtonProps> = ({ className, type, children }) => (
<button
type="button"
className={twMerge(CLASS_NAMES[type ?? 'primary'], className)}
>
{children}
</button>
)
今天就到這邊,明天就會開始出現 FP 相關內容囉 !