# TypeScript 基礎應用入門,並與 Next.js 進行整合開發
## 第 1 章:TypeScript 簡介與環境設置
**TypeScript = JS 超集 + 靜態型別系統**。藉由編譯期偵錯、完善的 IDE 體驗(自動完成、跳轉、重構),提升大型專案的可靠性與維護性。
### 推薦安裝方式(本地開發依賴)
> 不建議全域安裝 `tsc` 當作日常開發主流程,改用專案本地依賴更易控版本。
```bash
# 初始化專案(空資料夾)
npm init -y
# 安裝 TS 與型別(開發依賴)
npm i -D typescript @types/node
# 產生 tsconfig.json
npx tsc --init
```
### tsconfig 建議(現代前端)
```jsonc
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Node",
"jsx": "react-jsx",
"strict": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"include": ["src"]
}
```
> 若需 IE11(極少數舊專案)才考慮 `target: "ES5"`;現代 React 不再支援 IE11。
### Hello, TypeScript
```ts
// src/hello.ts
console.log("Hello, TypeScript!")
```
```bash
npx tsc src/hello.ts && node src/hello.js
```
參考:`feature/typescript`、`feature/typescript-hello`(你的範例倉庫)。
---
## 第 2 章:基本型別(精要)
* **number / string / boolean**:`let n: number = 42`、模板字面量型別可組裝字串。
* **null / undefined**:常見於可空欄位,與 `strictNullChecks` 搭配更安全。
* **any vs unknown**:`unknown` 更安全(需先縮小/斷言才能使用)。
* **void / never**:`void` 表示不回傳;`never` 表示永不返回(throw 或無限迴圈)。
* **object**:非原始型別;在專案中更常使用自定義物件型別。
* **symbol**:唯一鍵;偶見於進階框架開發。
* **型別推斷**:TS 會從初始值推斷型別,善用推斷可減少噪音。
---
## 第 3 章:變數、常量與作用域
```ts
let age: number = 25
const name = "John" // const 推斷為字面量型別,可更精準
// name = "Doe" // ❌ 不能再賦值
```
> 與 `var` 相比,`let/const` 具塊級作用域,避免提升與重複宣告問題。
---
## 第 4 章:函式與箭頭函式
```ts
function greet(name: string = "Anonymous"): string {
return `Hello, ${name}!`
}
// 箭頭函式 + 明確回傳型別
const add = (a: number, b: number): number => a + b
// 可選參數
function log(msg: string, level?: "info" | "warn" | "error") {
console[level ?? "info"]?.(msg)
}
// 函式多載(overload)
function toArray(x: string): string[]
function toArray(x: number): number[]
function toArray(x: string | number) { return [x] }
```
---
## 第 5 章:物件、陣列、元組
```ts
// 物件
const person: { name: string; age?: number; readonly id: string } = {
id: "u_1", name: "Alice"
}
// 陣列
const nums: number[] = [1,2,3]
const words: Array<string> = ["a","b"] // 等價寫法
// 只讀陣列
const readonlyNums: readonly number[] = [1,2,3]
// readonlyNums.push(4) // ❌
// 元組
const userEntry: [id: string, score: number] = ["u_1", 98]
// 聯合型別陣列
const mixed: (number | string)[] = [1, "two", 3]
```
---
## 第 6 章:`type` 與 `interface`
**相同點**:都可描述物件形狀、支援可選屬性與函式型別。
**差異與建議**:
* `interface` 支援多次宣告合併、`extends`(在 SDK/公共 API 常用)。
* `type` 支援 **聯合 / 交叉 / 條件 / 映射** 型別,組合力更強。
* 在 React 專案中:
* **Props/State** 可用 `type` 或 `interface` 皆可;偏好多用 `type` 來搭配聯合與工具型別。
```ts
type User = { id: string; name: string }
interface Node { id: string }
interface Person extends Node { name: string }
type Employee = User & { role: "dev" | "pm" }
```
---
## 第 7 章:Class(瞭解即可)
React 函式元件為主流,Class 在前端較少見;但在工具程式或 OOP 情境仍實用。
```ts
class Dog {
breed = "Mixed"
name = "Unknown"
bark() { console.log("Woof!") }
}
const d = new Dog(); d.name = "Buddy"; d.bark()
```
---
## 第 8 章:Enum 與替代方案
```ts
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" }
```
> 前端專案更常用 **字串聯合型別** 取代 `enum`,因為 tree-shaking 友善、可讀值直接就是字串:
```ts
type Direction2 = "UP" | "DOWN" | "LEFT" | "RIGHT"
```
> `const enum` 會 inline 值、體積小,但與 `isolatedModules`/Babel 流程相處不佳,慎用。
---
## 第 9 章:泛型(Generics)
```ts
function identity<T>(x: T): T { return x }
const a = identity<string>("hi")
const b = identity(42) // 推斷為 number
class Box<T> { constructor(public value: T) {} }
const s = new Box<string>("txt")
interface Pair<K, V> { key: K; value: V }
const p: Pair<string, number> = { key: "age", value: 30 }
// 受限泛型
interface HasLength { length: number }
function logLength<T extends HasLength>(item: T) { console.log(item.length) }
```
---
# 第二部分:在 React 中使用 TypeScript
## 建立 React + TS 專案
**Vite(推薦)**
```bash
npm create vite@latest my-app -- --template react-ts
cd my-app && npm i && npm run dev
```
**Create React App(舊方案)**
```bash
npx create-react-app my-app --template typescript
```
## tsconfig(React 專案範例)
```jsonc
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Node",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src"]
}
```
**Vite 路徑別名**(`vite.config.ts`)
```ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
resolve: { alias: { '@': path.resolve(__dirname, 'src') } }
})
```
## 元件與 Props 型別
```tsx
// 方式一:具名 Props 型別 + 傳統函式
type GreetingProps = { name: string; children?: React.ReactNode }
function Greeting({ name, children }: GreetingProps) {
return <h1>Hello, {name}! {children}</h1>
}
// 方式二:React.FC(注意 children 已內建,但不易自訂 defaultProps)
interface ButtonProps { onClick?: () => void; disabled?: boolean }
const Button: React.FC<ButtonProps> = ({ children, ...rest }) => (
<button {...rest}>{children}</button>
)
```
> 實務上 **不一定要用 `React.FC`**;直接為函式標註 props 型別即可,較直觀。
## 事件與 DOM 型別
```tsx
function SearchBox() {
const [q, setQ] = React.useState("")
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setQ(e.target.value)
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault() }
return (
<form onSubmit={onSubmit}>
<input value={q} onChange={onChange} />
<button>Go</button>
</form>
)
}
```
## 狀態與可空型別
```tsx
const [user, setUser] = React.useState<null | { id: string; name: string }>(null)
// 之後以 type narrowing 使用 user?.name 或條件判斷
```
## Context 與自訂 Hook 型別
```tsx
// Context
type Auth = { user: { id: string; name: string } | null; login: () => void }
const AuthCtx = React.createContext<Auth | undefined>(undefined)
function useAuth() {
const ctx = React.useContext(AuthCtx)
if (!ctx) throw new Error('useAuth must be used within <AuthProvider>')
return ctx
}
```
## CSS Modules 型別宣告
```ts
// src/global.d.ts
declare module '*.module.css' { const classes: Record<string, string>; export default classes }
declare module '*.module.scss' { const classes: Record<string, string>; export default classes }
```
## 常見第三方型別
```bash
npm i -D @types/react-router-dom @types/lodash
```
> 很多套件已內建型別,否則在 DefinitelyTyped 以 `@types/` 取得。
---
# 第三部分:在現有 React 專案中引入 TypeScript(增量遷移)
## 步驟總覽
1. 安裝依賴:
```bash
npm i -D typescript @types/node @types/react @types/react-dom
```
2. 新增 `tsconfig.json`(見上方建議)。
3. **逐檔改名**:`.js`→`.ts`、`.jsx`→`.tsx`。
4. **允許混用**:暫時保留 `.js`(`allowJs: true`)。也可用 `// @ts-check` + JSDoc 在 JS 中先享型別檢查。
5. 逐步加入型別註解(props、state、事件、API 回傳)。
6. `npx tsc --noEmit` 持續檢查,修正錯誤。
參考:`feature/app`、`feature/react-typescript-init`(你的範例倉庫)。
## 例:JS → TS 改寫
```tsx
// App.tsx
import { useState } from 'react'
export default function App() {
const [count, setCount] = useState<number>(0)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
```
## API 回傳型別與資料安全
```ts
type Todo = { id: number; title: string; completed: boolean }
async function fetchTodos(): Promise<Todo[]> {
const r = await fetch('/api/todos')
const data = await r.json() as Todo[] // 更安全:配合 zod/io-ts 驗證
return data
}
```
---
# 第四部分:工具化(ESLint / Prettier / tsconfig 嚴格度)
## ESLint + typescript-eslint(Vite 預設可擴充)
```bash
npm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
```
```js
// .eslintrc.cjs
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended'
],
settings: { react: { version: 'detect' } }
}
```
## Prettier(格式化)
```bash
npm i -D prettier eslint-config-prettier eslint-plugin-prettier
```
```js
// .prettierrc.json
{ "singleQuote": true, "semi": false, "trailingComma": "es5" }
```
## 嚴格度微調(tsconfig)
* `strict: true`(建議)
* 常用:`noImplicitAny`, `strictNullChecks`, `noUncheckedIndexedAccess`(更嚴謹)
---
# 第五部分:常見踩坑與最佳實務
* **`any` 氾濫** → 以 `unknown` / 型別守衛 / 泛型替代。
* **`React.FC` 一律使用?** → 不必。直接為函式標 props 型別更靈活。
* **`defaultProps` in FC** → 已不推薦;用參數預設值或 `Partial<T>`。
* **可空狀態** → 初始 `null` 會變 `T | null`,渲染前加守衛或 `?.`。
* **事件型別** → `React.ChangeEvent<HTMLInputElement>`、`React.MouseEvent<HTMLButtonElement>`。
* **ref / forwardRef** → `const Comp = React.forwardRef<HTMLDivElement, Props>((props, ref) => ...)`。
* **CSS Modules 型別** → 記得加 `global.d.ts` 宣告。
* **路徑別名** → tsconfig + bundler(Vite/webpack)需一致。
* **Enum vs 字串聯合** → 前端多用字串聯合;必要時才上 enum。
* **工具型別** → `Partial`/`Pick`/`Omit`/`Record`/`ReturnType`/`Parameters`/`NonNullable` 善用。
* **`satisfies`**(TS 4.9+) → 保留推斷又驗證結構:
```ts
const routes = {
home: { path: '/' },
about: { path: '/about' }
} as const satisfies Record<string, { path: string }>
```
---
# 第六部分:小型實作練習清單
1. **型別化 TodoList**:`Todo` 型別、API 型別、`useReducer` 管理狀態。
2. **表單元件庫**:輸入元件 `value/onChange` 泛型化,事件型別正確。
3. **資料抓取 hooks**:`useFetch<T>()` 泛型回傳,搭配 `AbortController`。
4. **路由型別**:`useParams<{ id: string }>()`(React Router v6)。
5. **Context + Reducer**:以型別守衛確保 `dispatch` 的 action payload 正確。
---
## 參考與你的範例倉庫
* 初始化專案:`feature/typescript`、`feature/typescript-hello`
* React TS 起步與遷移:`feature/app`、`feature/react-typescript-init`
> 完成本教材後,你應能:讀寫 TS 型別、在 React 中自信地型別化所有常見模式、把既有 JS 專案 **增量遷移** 至 TypeScript,並建立一套可靠的團隊規範。