Next.js 14 App Router 的基础用法
前言
本文专为刚接触Next.js 14的读者量身定制,以简明易懂的语言介绍Next.js的App Router机制,帮助你快速掌握其核心概念和用法
demo源码地址:next-ts-seed
1. 静态路由
Next.js与React不同,Next.js提供了一套内置的文件路由系统,文件的命名与路由的生成有着直接的对应关系
具体来说,app目录下的page.tsx文件会自动映射为页面的路由,例如,app/page.tsx会被解析为根路由“/”。同样地,app/login/page.tsx则会代表着“/login”这一路由。
目录结构 | 对应路由 |
---|---|
app/page.js | / |
app/dashboard/page.js | /dashboard |
app/dashboard/settings/page.js | /dashboard/settings |
2. 嵌套路由
layout.tsx会自动映射为多个路由之间共享的UI组件
1. 根布局
在app目录下放置的layout.tsx文件将作为根布局,它是所有路由共用的UI基础。在这个文件中,可以引入全局样式、公共组件等,确保它们能够应用到整个应用程序的每一个页面。
根布局必须包含html和body标签
//app/layout.tsx
const RootLayout = ({ children }: React.PropsWithChildren) => (
<html lang="en">
<body>
{children}
</body>
</html>
);
export default RootLayout;
2. 嵌套布局
在app目录的各个子文件夹下创建layout.tsx文件。这些子文件夹下的layout.tsx文件将作为该路由下的共享UI组件,仅在该子文件夹下的页面之间共享。
- 新建src/app/home/layout.tsx
export default function HomeLayout({ children }: { children: React.ReactNode }) {
return (
<section>
布局页面
{children}
</section>
);
}
- 新建src/app/home/user/page.tsx
const User = () => <div>用户页面</div>;
export default User;
- 此时的目录结构为:
上面的路由形式可以理解为react-router-dom V6的嵌套路由配置
const routes = [
{
element: <HomeLayout />,
children: [
{
path: '/home/user',
element: <Manage />,
},
],
},
];
- 访问
http://localhost:3001/home/user
查看效果(默认端口号为3000,我这里启动端口修改成了3001)
3. 动态路由
在Next.js中,要实现动态路由,你需要在pages目录的相应位置使用方括号来创建特殊的文件夹名称,例如[id]或[slug]。动态字段将作为参数传递给layout、page、route和generateMetadata函数
1. [folderName]
动态路由的基本用法,新建src/app/blog/[id]/page.tsx
const Blog = ({ params }: { params: { id: string } }) => {
return <div>{params.id}博客页面</div>;
};
export default Blog;
访问路由“/blog/2024”,页面将呈现“2024博客页面”
2. [...folderName]
如果访问路由“/blog/2024/1”,则页面会显示404。可在方括号内添加省略号[...folderName],捕获后面所有的路由片段
- 新建src/app/blog/[...year]/page.tsx
const Blog = ({ params }: { params: { year: string[] } }) => {
return (
<div>
{params.year.map((o, index) => (
<div key={index}>{o}</div>
))}
</div>
);
};
export default Blog;
访问路由“/blog/2024/1”,页面将会显示20241
目录结构,对应路由与-参数的关系如下所示:
目录结构 | 对应路由 | 参数 |
---|---|---|
app/blog/[...year]/page.tsx | /blog/a | { year: ['a'] } |
app/blog/[...year]/page.tsx | /blog/a/b | { year: ['a','b'] } |
app/blog/[...year]/page.tsx | /blog/a/b/c | { year: ['a','b','c] } |
3. [[...folderName]]
blog/[...year]能捕获/blog/a,也能捕获/blog/a/b,但不能捕获/blog,访问/blog将会得到404页面。可通过在双方括号内添加省略号的形式,捕获包括自身的路由段。
将上述[...year]修改为[[...year]],访问/blog得到的对应参数为空对象 {},因此再修改下src/app/blog/[[...year]]/page.tsx页面
const Blog = ({ params }: { params: { year: string[] } }) => {
return (
<div>
{params.year ? (
params.year.map((o, index) => <span key={index}>{o}</span>)
) : (
<span>blog页面</span>
)}
</div>
);
};
export default Blog;
访问/blog将看到页面显示“blog页面”
4. 路由组
路由组是为了在项目比较大时,更好的管理路由文件的一种方式。使用中括号来创建特殊的文件夹名称,例如(marketing)或(shop)。
路由组不会影响url结构,例如app/(marketing)/about/page.js对应的路由为/about
5. 配置404页面
not-found.tsx文件用于处理页面未找到的情况
新建src/app/not-found.tsx
export default function NotFound() {
return <div>我是自定义404页面</div>;
}
6. 声明式、编程式导航
1. 声明式导航
使用Link组件进行路由跳转
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
如果想要增加激活状态的样式,需使用usePathname()
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<ul>
<li>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
</li>
<li>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</li>
</ul>
</nav>
)
}
2. 编程式导航
使用hook进行路由跳转
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
因为使用了hook,因此在这里需使用'use client',显式声明是客户端组件
7. 路由传参
1. 动态路由匹配
- 定义路由跳转
修改src/app/page.tsx
import Link from 'next/link';
const Home = () => (
<div className="App">
<Link href={'/blog/1'}>博客</Link>
</div>
);
export default Home;
- 获取参数
(1)在服务端组件中,使用params获取
使用方括号来创建动态路由文件,上文已创建过src/app/blog/[id]/page.tsx
const Blog = ({ params }: { params: { id: string } }) => {
return <div>{params.id}博客页面</div>;
};
export default Blog;
(2)在客户端组件中,使用useParams获取
修改src/app/blog/[id]/page.tsx
'use client';
import { useParams } from 'next/navigation';
export default function Blog() {
const params = useParams();
return <div>{params.id}博客页面</div>;
}
2. query 传参
- 编程式传参
修改src/app/page.tsx
import Link from 'next/link';
const Home = () => (
<div className="App">
<Link
href={{
pathname: '/new',
query: {
sort: 'name',
id: '2',
},
}}
>
新闻页
</Link>
</div>
);
export default Home;
- 命令式传参
修改src/app/page.tsx
'use client';
import { useRouter } from 'next/navigation';
const Home = () => {
const router = useRouter();
return (
<div className="App">
<button onClick={() => router.push('/new?sort=name&id=2')}>新闻页</button>
</div>
);
};
export default Home;
- 获取参数
新建src/app/new/page.tsx
'use client';
import { Suspense } from 'react';
import { useSearchParams } from 'next/navigation';
function New() {
const searchParams = useSearchParams();
return <Suspense>Search: {searchParams.get('id')}</Suspense>;
}
export default function Page() {
return (
<Suspense>
<New />
</Suspense>
);
}
8. 错误处理
在路由组件加载过程中发生错误时展示的元素
error.tsx文件用于处理页面报错的情况
- 新建src/app/error.tsx
'use client';
import { useEffect } from 'react';
export default function Error({ error }: { error: Error & { digest?: string } }) {
useEffect(() => {
// 收集错误日志,发送给服务端
console.error('11', error);
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
</div>
);
}
提示:error.tsx只能为客户端组件
- 新建src/app/user/page.tsx
const obj: any = {
a: '2',
};
export default function New() {
return (
<div>
{/* 报错:因为取不到c属性 */}
{obj.b.c}
</div>
);
}
访问/user路由,可以看到页面显示为“Something went wrong!”
结尾
本文介绍了App Router的基本用法,想了解App Router更多用法的,可参考我的其他文章:
参考资料:
转载自:https://juejin.cn/post/7344598656144195647