iT邦幫忙

2023 iThome 鐵人賽

DAY 3
0

經過了兩天廢話,我們終於要開始寫程式啦~ ٩(ˊᗜˋ)و✧。**

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。

https://ithelp.ithome.com.tw/upload/images/20230918/20158615QiSDxomQK5.png

上面 flex min-h-screen items-center justify-center 這串文字其實就是 tailwind 設定。每個空格分隔的字串都對應到一個 css 屬性,例如 flex 對應到 css 的 display: flex,而items-center 則對應到 align-items:center,這些對應關係在 Tailwind 文件 中都找的到。

更改標題與 favicon

網頁的外框,由 src/app/layout.tsx 決定

export const metadata: Metadata = {
  title: 'Course Management System',  //before: Create Next App
  description: 'Generated by create next app',
}

當我們把它的 title 改掉,就能看到瀏覽器頁籤發生變化

https://ithelp.ithome.com.tw/upload/images/20230918/201586151qkFuc2Zfs.png

當然 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 看到以下畫面。

https://ithelp.ithome.com.tw/upload/images/20230918/20158615tYE7aqHdkG.png

新增 Tailwind 元件

無論是表格、標題列或是表單,我們都可以在 flowbite 找到對應的 Tailwind CSS 範本。使用這些範本可以省下大量手動設定元件外觀的時間,同時保留客製化的可能性。

使用 Snippets

開發過程我們會新增大量的元件,以下擴充功能可以藉由關鍵字快速生成常用 react 程式碼 Snippets,省下重複打字時間。

https://ithelp.ithome.com.tw/upload/images/20230918/20158615O4fqExFVAN.png

例如以下程式碼使用關鍵字 rface 就可以瞬間產生

import React from 'react'

const Page = () => {
	return (
		<div>Page</div>
	)
}

export default Page

複製貼上範例 HTML

有了 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>

覺得手動替換麻煩的話也可以利用擴充功能來轉換。

https://ithelp.ithome.com.tw/upload/images/20230918/20158615Svh2RkViYI.png

調整暗黑模式

https://ithelp.ithome.com.tw/upload/images/20230918/20158615avAcP1e7Uw.png

我的系統是預設開啟暗黑模式的,因此會看到畫面上顯示的也是暗黑模式的元件。如果要手動控制暗黑模式,我們可以進行以下設定。

  1. /tailwind.config.ts 新增 darkMode 設定

    const config: Config ={
      // ...
      darkMode: 'class'
      // ...
    }
    export default config
    
  2. 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>
      )
    }
    

https://ithelp.ithome.com.tw/upload/images/20230918/20158615Moyjd5MAiT.png

設定完變得怪怪的,這是因為 /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;
}

畫面
https://ithelp.ithome.com.tw/upload/images/20230918/20158615WDNQ1232nR.png

新增需要 JS 的 Tailwind 元件

我們以 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>
  )
}

date range picker

範例參考

切換到分支 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 瀏覽當週課表

https://ithelp.ithome.com.tw/upload/images/20230918/20158615YNeX1TmeGz.png

範例網站預設開啟白天模式,可以前往 src/app/layout.tsx 切換為暗黑模式

// before
<html lang="en" className="light">
// after
<html lang="en" className="dark">

https://ithelp.ithome.com.tw/upload/images/20230918/20158615A4KXNXAf76.png

如何設計/拆分/抽取元件?

學會複製貼上元件後,很快就可以組出看起來還不錯的網頁。但隨著網頁複雜度增加,程式碼一個不注意就會往雜亂無章的深淵跌落,造成本來很開心寫網頁的心情漸漸煩躁起來。如果要避免這種情形就勢必要把通用的部份抽出來,然後依照資料夾分門別類的整理好。可是怎麼做呢? 好多元件、好多要注意的細節,如何讓人一目了然 ?

以下是我的一些習慣分享給大家

  • 元件超過 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>
)
  • Premature optimization is the root of all evil !
    重複三次以上再抽也不遲,先別幻想自己的 code 會有很多人用

今天就到這邊,明天就會開始出現 FP 相關內容囉 !


上一篇
D02 - 準備開發環境
下一篇
D04 - 設計資料模型
系列文
從 Next.js 開始的 Functional Programming30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言