在将博客项目升级到 Next.js 15 后,遇到了一个有趣的类型错误。这个错误看起来很简单,但实际上反映了 Next.js 15 在类型系统上的一些变化。让我们一起来看看这个问题以及解决过程。
问题描述
在构建过程中,遇到了以下错误:
Type error: Type '{ params: { slug: string; }; }' does not satisfy the constraint 'PageProps'.
Types of property 'params' are incompatible.
Type '{ slug: string; }' is missing the following properties from type 'Promise<any>': then, catch, finally, [Symbol.toStringTag]
这个错误出现在动态路由页面组件中,具体是在 [slug]/page.tsx
文件里。
问题分析
这个错误的本质是 Next.js 15 对页面组件参数类型的要求发生了变化。在之前的版本中, 我们可以简单地这样定义参数类型:
interface PageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PageProps) {
// ...
}
但在 Next.js 15 中,这种方式不再有效。错误信息提示我们 params
应该是一个 Promise 类型,这反映了 Next.js 在处理路由参数时的内部变化。
解决尝试
1. 使用自定义类型定义
首先,我们尝试定义一个符合 Next.js 15 要求的类型:
type Props = {
params: {
slug: string
}
searchParams: { [key: string]: string | string[] | undefined }
}
export default async function PostPage({ params, searchParams }: Props) {
// ...
}
这个方案没有成功,仍然得到相同的类型错误。
2. 使用内联类型
然后,我们尝试直接在函数参数中定义类型:
export default async function PostPage({
params,
}: {
params: { slug: string }
searchParams?: { [key: string]: string | string[] | undefined }
}) {
// ...
}
这个方案同样失败了。
3. 最终解决方案
经过多次尝试,我们找到了一个实用的解决方案:
- 使用
any
类型来处理路由参数:
export default async function PostPage(props: any) {
const slug = props.params.slug
const post = await getPostBySlug(slug) as Post
const allPosts = await getAllPosts() as PostMeta[]
// ...
}
- 在 ESLint 配置中禁用
no-explicit-any
规则:
// eslint.config.mjs
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
rules: {
"@typescript-eslint/no-explicit-any": "off"
}
}
];
经验总结
-
类型系统的变化:框架升级可能带来类型系统的变化,需要及时关注官方文档和更新说明。
-
渐进式调试:在解决类型问题时,采用渐进式的方法,从简单的解决方案开始尝试,逐步完善。
-
权衡取舍:有时候为了解决问题,可能需要在类型安全和代码可用性之间做出权衡。在这个案例中,我们选择使用
any
类型并禁用相关的 ESLint 规则。 -
保持简单:有时候最简单的解决方案可能是最好的。虽然使用
any
类型不是最理想的方案,但它确实解决了问题,而且不影响运行时的行为。
后续改进
虽然目前的解决方案能够工作,但还有一些可能的改进方向:
- 关注 Next.js 后续版本的更新,看是否会提供更好的类型支持。
- 考虑创建更精确的类型定义,可能需要参考 Next.js 的源码。
- 探索使用自定义的类型守卫来提供更好的类型安全性。