09. TypeScript与前端框架:React、Node.js

TypeScript 与前端框架:React、Node.js

TypeScript Frameworks

TypeScript 与现代前端框架是天生一对。这一期,我们学习 TypeScript 在 React 和 Node.js 中的应用。

React + TypeScript

创建项目

# Vite(推荐)
npm create vite@latest my-app -- --template react-ts

# Create React App
npx create-react-app my-app --template typescript

# Next.js
npx create-next-app@latest my-app --typescript

组件类型

函数组件

// 简单组件
const Button: React.FC = () => {
  return <button>Click me</button>
}

// 带 Props 的组件
interface ButtonProps {
  text: string
  onClick: () => void
  disabled?: boolean
}

const Button: React.FC = ({ 
  text, 
  onClick, 
  disabled = false 
}) => {
  return (
    <button disabled="{disabled}">
      {text}
    </button>
  )
}

内联 Props

type UserCardProps = {
  name: string
  email: string
  avatar?: string
}

function UserCard({ name, email, avatar }: UserCardProps) {
  return (
    <div>
      {avatar && <img src="{avatar}" alt="{name}" />}
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  )
}

Hooks 类型

useState

// 类型推断
const [count, setCount] = useState(0)

// 显式类型
const [user, setUser] = useState(null)

// 复杂类型
const [items, setItems] = useState([])

useEffect

useEffect(() => {
  // 副作用逻辑
  return () => {
    // 清理函数
  }
}, [dependency])

useRef

// DOM 引用
const inputRef = useRef(null)

// 任意值引用
const timerRef = useRef(null)

useContext

interface ThemeContextType {
  theme: 'light' | 'dark'
  toggleTheme: () => void
}

const ThemeContext = createContext(null)

function useTheme() {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider')
  }
  return context
}

useReducer

type State = {
  count: number
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset'; payload: number }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    case 'reset':
      return { count: action.payload }
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0 })

事件类型

function Form() {
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
  }

  const handleChange = (e: React.ChangeEvent) => {
    console.log(e.target.value)
  }

  const handleClick = (e: React.MouseEvent) => {
    console.log('clicked')
  }

  return (

      <button>Submit</button>

  )
}

泛型组件

interface ListProps {
  items: T[]
  renderItem: (item: T) => React.ReactNode
}

function List({ items, renderItem }: ListProps) {
  return (
    <ul>
      {items.map((item, index) => (
        <li>{renderItem(item)}</li>
      ))}
    </ul>
  )
}

// 使用
 <span>{user.name}</span>}
/>

高阶组件

interface WithLoadingProps {
  loading?: boolean
}

function withLoading<P>(
  Component: React.ComponentType<P>
): React.FC<P> {
  return ({ loading, ...props }) => {
    if (loading) {
      return <div>Loading...</div>
    }
    return 
  }
}

Node.js + TypeScript

项目设置

mkdir my-project
cd my-project
npm init -y
npm install typescript ts-node @types/node --save-dev
npx tsc --init

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Express 示例

npm install express
npm install @types/express --save-dev
import express, { Request, Response, NextFunction } from 'express'

const app = express()
app.use(express.json())

// 类型定义
interface User {
  id: number
  name: string
  email: string
}

// 内存数据库
const users: User[] = []

// 路由
app.get('/users', (req: Request, res: Response) => {
  res.json(users)
})

app.get('/users/:id', (req: Request, res: Response) => {
  const user = users.find(u => u.id === parseInt(req.params.id))
  if (!user) {
    res.status(404).send('User not found')
    return
  }
  res.json(user)
})

app.post('/users', (req: Request<{}, User, Omit>, res: Response) => {
  const newUser: User = {
    id: users.length + 1,
    ...req.body
  }
  users.push(newUser)
  res.status(201).json(newUser)
})

// 错误处理
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack)
  res.status(500).json({ error: err.message })
})

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000')
})

中间件类型

interface AuthRequest extends Request {
  user?: {
    id: number
    role: string
  }
}

function authMiddleware(
  req: AuthRequest,
  res: Response,
  next: NextFunction
) {
  const token = req.headers.authorization
  if (!token) {
    res.status(401).json({ error: 'Unauthorized' })
    return
  }

  // 验证 token...
  req.user = { id: 1, role: 'admin' }
  next()
}

app.get('/protected', authMiddleware, (req: AuthRequest, res: Response) => {
  res.json({ user: req.user })
})

数据库集成

// Prisma 示例
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function getUsers() {
  const users = await prisma.user.findMany({
    include: { posts: true }
  })
  return users
}

async function createUser(data: { name: string; email: string }) {
  const user = await prisma.user.create({
    data
  })
  return user
}

工具函数

API 客户端

interface HttpClient {
  get(url: string): Promise
  post(url: string, data: unknown): Promise
  put(url: string, data: unknown): Promise
  delete(url: string): Promise
}

class FetchHttpClient implements HttpClient {
  private baseUrl: string

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl
  }

  async get(url: string): Promise {
    const response = await fetch(<code>${this.baseUrl}${url})
    if (!response.ok) {
      throw new Error(HTTP error! status: ${response.status})
    }
    return response.json()
  }

  async post(url: string, data: unknown): Promise {
    const response = await fetch(${this.baseUrl}${url}, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    })
    return response.json()
  }

  // ... 其他方法
}

状态管理

// 简单的状态管理器
type Listener = (state: T) => void

class Store {
  private state: T
  private listeners: Listener[] = []

  constructor(initialState: T) {
    this.state = initialState
  }

  getState(): T {
    return this.state
  }

  setState(newState: Partial) {
    this.state = { ...this.state, ...newState }
    this.listeners.forEach(listener => listener(this.state))
  }

  subscribe(listener: Listener) {
    this.listeners.push(listener)
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener)
    }
  }
}

// 使用
interface AppState {
  user: User | null
  loading: boolean
}

const store = new Store({
  user: null,
  loading: false
})

测试

Jest 配置

npm install --save-dev jest ts-jest @types/jest
npx ts-jest config:init

单元测试

// math.ts
export function add(a: number, b: number): number {
  return a + b
}

// math.test.ts
import { add } from './math'

describe('add', () => {
  it('should add two numbers', () => {
    expect(add(1, 2)).toBe(3)
  })
})

React 组件测试

// Button.tsx
interface ButtonProps {
  text: string
  onClick: () => void
}

export const Button: React.FC = ({ text, onClick }) => {
  return <button>{text}</button>
}

// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'

describe('Button', () => {
  it('should render with text', () => {
    render(<Button> {}} />)
    expect(screen.getByText('Click me')).toBeInTheDocument()
  })

  it('should call onClick when clicked', () => {
    const handleClick = jest.fn()
    render(<Button />)
    fireEvent.click(screen.getByText('Click me'))
    expect(handleClick).toHaveBeenCalledTimes(1)
  })
})

实战技巧

类型安全的 API 路由

// 路由定义
type Route = {
  path: string
  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
  handler: (req: Request, res: Response) => void | Promise
}

const routes: Route[] = [
  {
    path: '/users',
    method: 'GET',
    handler: getUsers
  },
  {
    path: '/users',
    method: 'POST',
    handler: createUser
  }
]

// 自动注册路由
routes.forEach(route => {
  app[route.method.toLowerCase()](route.path, route.handler)
})

类型安全的环境变量

// env.ts
import { z } from 'zod'

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']),
  PORT: z.string().transform(Number),
  DATABASE_URL: z.string(),
  JWT_SECRET: z.string()
})

export const env = envSchema.parse(process.env)

总结

TypeScript 与框架结合要点:

  • React:组件类型、Hooks 类型、事件类型
  • Node.js:Express 类型、中间件、数据库
  • 测试:Jest + ts-jest
  • 最佳实践:类型安全的 API、环境变量

下一期,我们学习 Vue3 + TypeScript 的完整集成指南。


框架文档

Views: 0