Next.js TypeScript集成 #

一、TypeScript配置 #

1.1 tsconfig.json #

json
{
    "compilerOptions": {
        "target": "ES2017",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "strict": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "esnext",
        "moduleResolution": "bundler",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "preserve",
        "incremental": true,
        "plugins": [{ "name": "next" }],
        "paths": {
            "@/*": ["./src/*"]
        }
    },
    "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
    "exclude": ["node_modules"]
}

1.2 严格模式 #

json
{
    "compilerOptions": {
        "strict": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true
    }
}

二、类型定义 #

2.1 组件Props #

tsx
interface ButtonProps {
    variant?: 'primary' | 'secondary' | 'outline'
    size?: 'sm' | 'md' | 'lg'
    disabled?: boolean
    loading?: boolean
    children: React.ReactNode
    onClick?: () => void
}

export function Button({
    variant = 'primary',
    size = 'md',
    disabled = false,
    loading = false,
    children,
    onClick,
}: ButtonProps) {
    return (
        <button
            className={cn(styles.base, styles[variant], styles[size])}
            disabled={disabled || loading}
            onClick={onClick}
        >
            {loading ? <Spinner /> : children}
        </button>
    )
}

2.2 页面Props #

tsx
interface PageProps {
    params: Promise<{ id: string }>
    searchParams: Promise<{ page?: string }>
}

export default async function ProductPage({ params, searchParams }: PageProps) {
    const { id } = await params
    const { page = '1' } = await searchParams
    
    const product = await getProduct(id)
    
    return <ProductDetails product={product} />
}

2.3 API响应类型 #

tsx
interface ApiResponse<T> {
    data: T
    message: string
    success: boolean
}

interface PaginatedResponse<T> extends ApiResponse<T[]> {
    pagination: {
        page: number
        limit: number
        total: number
        totalPages: number
    }
}

export async function getProducts(): Promise<PaginatedResponse<Product>> {
    const res = await fetch('/api/products')
    return res.json()
}

2.4 环境变量类型 #

typescript
namespace NodeJS {
    interface ProcessEnv {
        NEXT_PUBLIC_API_URL: string
        DATABASE_URL: string
        SECRET_KEY: string
        NEXT_PUBLIC_GA_ID?: string
    }
}

三、类型安全 #

3.1 API路由类型 #

tsx
import { NextRequest, NextResponse } from 'next/server'

interface RouteContext {
    params: Promise<{ id: string }>
}

export async function GET(
    request: NextRequest,
    { params }: RouteContext
): Promise<NextResponse<ApiResponse<User>>> {
    const { id } = await params
    const user = await getUser(id)
    
    return NextResponse.json({
        data: user,
        message: 'Success',
        success: true,
    })
}

3.2 Server Actions类型 #

tsx
'use server'

interface FormState {
    error?: string
    success?: boolean
}

interface FormData {
    title: string
    content: string
}

export async function createPost(
    prevState: FormState,
    formData: FormData
): Promise<FormState> {
    try {
        await db.post.create({ data: formData })
        return { success: true }
    } catch {
        return { error: '创建失败' }
    }
}

3.3 组件类型 #

tsx
import { ComponentProps, Ref } from 'react'

type ButtonBaseProps = ComponentProps<'button'>

interface CustomButtonProps extends ButtonBaseProps {
    variant?: 'primary' | 'secondary'
    loading?: boolean
}

export const Button = forwardRef<HTMLButtonElement, CustomButtonProps>(
    ({ variant = 'primary', loading, children, ...props }, ref) => {
        return (
            <button ref={ref} {...props}>
                {loading ? <Spinner /> : children}
            </button>
        )
    }
)

Button.displayName = 'Button'

四、泛型组件 #

4.1 列表组件 #

tsx
interface ListProps<T> {
    items: T[]
    renderItem: (item: T, index: number) => React.ReactNode
    keyExtractor: (item: T) => string
    emptyMessage?: string
}

export function List<T>({
    items,
    renderItem,
    keyExtractor,
    emptyMessage = '暂无数据',
}: ListProps<T>) {
    if (items.length === 0) {
        return <div className="text-center text-gray-500">{emptyMessage}</div>
    }
    
    return (
        <ul>
            {items.map((item, index) => (
                <li key={keyExtractor(item)}>
                    {renderItem(item, index)}
                </li>
            ))}
        </ul>
    )
}

4.2 表单组件 #

tsx
interface FormProps<T> {
    defaultValues: T
    onSubmit: (data: T) => void
    children: (methods: FormMethods<T>) => React.ReactNode
}

export function Form<T extends Record<string, any>>({
    defaultValues,
    onSubmit,
    children,
}: FormProps<T>) {
    const methods = useForm({ defaultValues })
    
    return (
        <form onSubmit={methods.handleSubmit(onSubmit)}>
            {children(methods)}
        </form>
    )
}

五、类型工具 #

5.1 工具类型 #

typescript
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

type Required<T, K extends keyof T> = Omit<T, K> & { [P in K]-?: T[P] }

type Readonly<T> = { readonly [P in keyof T]: T[P] }

type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

5.2 API类型 #

typescript
type ApiSuccess<T> = {
    success: true
    data: T
}

type ApiError = {
    success: false
    error: string
}

type ApiResult<T> = ApiSuccess<T> | ApiError

六、最佳实践 #

6.1 避免any #

tsx
// 不推荐
function processData(data: any) {
    return data.value
}

// 推荐
interface Data {
    value: string
}

function processData(data: Data) {
    return data.value
}

6.2 使用类型推断 #

tsx
const user = await getUser(id)
// TypeScript 自动推断 user 的类型

6.3 使用const断言 #

tsx
const ROUTES = {
    HOME: '/',
    LOGIN: '/login',
    DASHBOARD: '/dashboard',
} as const

type Routes = typeof ROUTES[keyof typeof ROUTES]

七、总结 #

TypeScript集成要点:

要点 说明
配置 tsconfig.json
Props类型 接口定义
API类型 响应类型
环境变量 类型扩展
泛型组件 可复用性

下一步,让我们学习测试策略!

最后更新:2026-03-28