my-first-ai-web_问题记录02:Next.js 15 动态路由参数处理

问题

Next.js 15 中,访问动态路由参数会报错:

bash 复制代码
Error: Route "/work/[id]" used `params.id`. `params` should be awaited before using its properties.

这是一个破坏性变更 。Next.js 15 将动态路由参数 params 改为 Promise 形式,目的是为了:

  • 提高性能,支持流式渲染
  • 统一异步数据处理模式
  • 为未来的并发特性做准备

两种解决方法

什么是服务器组件和客户端组件?

在 Next.js 中:

  • 服务器组件:在服务器上运行,可以直接访问数据库,不能使用浏览器 API(默认模式)
  • 客户端组件 :在浏览器中运行,可以使用点击事件、状态等交互功能(需要添加 'use client')

方法一:async/await(服务器组件用)

适用于 :普通页面、布局文件(没有 'use client' 的组件)

tsx 复制代码
// app/work/[id]/page.tsx
export default async function WorkPage({ 
  params 
}: { 
  params: Promise<{ id: string }> 
}) {
  // 等待获取参数
  const { id } = await params;
  
  return (
    <div>
      <h1>工作项目 {id}</h1>
    </div>
  );
}
tsx 复制代码
// app/work/[id]/layout.tsx
export default async function Layout({
  params,
  children,
}: {
  params: Promise<{ id: string }>;
  children: React.ReactNode;
}) {
  const { id } = await params;
  
  return (
    <div>
      <aside>侧边栏 - 项目 {id}</aside>
      <main>{children}</main>
    </div>
  );
}

方法二:use() 函数(客户端组件用)

适用于 :有交互功能的组件(带 'use client' 的组件)

tsx 复制代码
// app/work/[id]/components/WorkButton.tsx
'use client'; // 客户端组件标记

import { use } from 'react';

export default function WorkButton({
  params
}: {
  params: Promise<{ id: string }>;
}) {
  // 使用 use() 获取参数
  const { id } = use(params);
  
  const handleClick = () => {
    alert(`编辑项目 ${id}`);
  };
  
  return (
    <button onClick={handleClick}>
      编辑项目 {id}
    </button>
  );
}

完整示例

页面文件(服务器组件)

tsx 复制代码
// app/work/[id]/page.tsx
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  
  return (
    <div className="p-8">
      <h1>项目详情 {id}</h1>
      <p>这是项目 {id} 的内容</p>
    </div>
  );
}

布局文件(服务器组件)

tsx 复制代码
// app/work/[id]/layout.tsx
export default async function Layout({
  params,
  children,
}: {
  params: Promise<{ id: string }>;
  children: React.ReactNode;
}) {
  const { id } = await params;
  
  return (
    <div className="flex">
      <nav className="w-64 p-4">
        <h2>项目 {id} 导航</h2>
      </nav>
      <div className="flex-1">
        {children}
      </div>
    </div>
  );
}

交互组件(客户端组件)

tsx 复制代码
// app/work/[id]/components/ProjectActions.tsx
'use client';

import { use, useState } from 'react';

export default function ProjectActions({
  params
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = use(params);
  const [liked, setLiked] = useState(false);
  
  return (
    <div>
      <button onClick={() => setLiked(!liked)}>
        {liked ? '❤️' : '🤍'} 项目 {id}
      </button>
      <button onClick={() => alert(`分享项目 ${id}`)}>
        分享项目 {id}
      </button>
    </div>
  );
}

如何选择方法

组件类型 特征 使用方法
服务器组件 - 没有 'use client' - 不能有点击事件 - 页面、布局文件 async/await
客户端组件 - 有 'use client' - 有点击、输入等交互 - 使用 useState use() 函数

修改步骤

  1. 找到所有使用 params 的文件
  2. 判断是服务器组件还是客户端组件
  3. 修改代码:

服务器组件:

tsx 复制代码
// 修改前
export default function Page({ params }) {
  return <div>{params.id}</div>;
}

// 修改后  
export default async function Page({ params }) {
  const { id } = await params;
  return <div>{id}</div>;
}

客户端组件:

tsx 复制代码
// 修改前
'use client';
export default function Component({ params }) {
  return <div>{params.id}</div>;
}

// 修改后
'use client';
import { use } from 'react';
export default function Component({ params }) {
  const { id } = use(params);
  return <div>{id}</div>;
}

常见错误

❌ 错误写法

tsx 复制代码
// 服务器组件中使用 use()
export default function Page({ params }) {
  const { id } = use(params); // 错误!服务器组件应该用 async/await
  return <div>{id}</div>;
}

// 客户端组件中使用 async/await  
'use client';
export default async function Button({ params }) { // 错误!客户端组件不能是 async
  const { id } = await params;
  return <button>{id}</button>;
}

✅ 正确写法

tsx 复制代码
// 服务器组件用 async/await
export default async function Page({ params }) {
  const { id } = await params;
  return <div>{id}</div>;
}

// 客户端组件用 use()
'use client';
import { use } from 'react';
export default function Button({ params }) {
  const { id } = use(params);
  return <button>{id}</button>;
}
相关推荐
橙光成长2 小时前
PDF离线工具箱深度解析:4MB纯前端单文件,40+功能全本地运行,文件绝不上传
前端·pdf·1024程序员节
小poop9 小时前
CSS 基础与实战:从入门到精通网页美化
前端
Csvn10 小时前
运行时性能
前端
Never_every9911 小时前
位图转矢量工具哪个好?实测抠抠图AI位图转矢量工具
大数据·前端
paopaokaka_luck11 小时前
宠物门店经营管理系统(销售与库存联动、进货自动记账、 退换与退款关联、员工个人业绩、ECharts 经营分析、滞销识别与客户分层、宠物档案年龄计算)
前端·javascript·spring boot·mysql·echarts·mybatis·1024程序员节
空中湖12 小时前
用 React 写数据大屏:为什么我不建议用图表库
前端·react.js·前端框架
欣欣之王来了13 小时前
原始HTML插值:v-html指令的使用与安全风险
前端·学习·架构·项目·vue3教程
IT_陈寒13 小时前
Redis的LRU淘汰策略坑了我一整天
前端·人工智能·后端
计算机魔术师14 小时前
砍半价格、参数再涨,国产旗舰追到 Opus 鼻尖了
前端
特立独行的猫A14 小时前
10 MB 的桌面 AI 客户端:用仓颉 + 系统 WebView 替代 Electron
前端·harmonyos