Next.js是如何实现路由的?
1.文件系统路由
Next.js采用文件系统路由,pages
目录下的每个React组件文件都代表一个路由。
例如:
pages/index.js
->/
pages/a.js
->/a
pages/b/index.js
->/b
pages/b/c.js
->/b/c
文件路由的处理方式比较直观和文件结构相挂钩,这样处理开发者可以通过文件结构了解路由结构,提高代码的可维护性和易读性。
一些组件我们并不期望被访问到,需要做好做好权限处理。
当我们需要频繁修改路由时,就要求我们频繁修改文件结构,这点可能并不友好。
2.动态路由
Next.js 支持通过文件名 [param].js
创建动态路由。例如,要创建一个可以匹配任何 ID 的动态页面,你可以创建 pages/[id].js
javascript
import React from 'react';
import { useRouter } from 'next/router';
export default function Post() {
const router = useRouter();
const { id } = router.query;
return <div>Post ID: {id}</div>;
}
访问http://localhost:3001/any-id
但是如果我们访问pages/a
,还是会访问a.js,而非得到Post ID:a。由此可以得出结论,文件系统路由的优先级在动态路由之上。
Next如何进行路由导航?
1.使用next/link
组件
next/link
组件允许你在 Next.js 应用中进行客户端路由导航,而不需要刷新页面,下面我们创建一个Nav
组件,并且在index中引用它。
javascript
import React from 'react';
import Link from 'next/link';
export default () => {
return (
<div>
<Link href="/">
Home
</Link>
<br/>
<Link href="/a">
a
</Link>
<br/>
<Link href="/b/c">
b/c
</Link>
</div>
);
}
javascript
import React from 'react';
import Nav from '../components/Nav/nav.jsx';
export default () => {
return <>
<Nav />
</>
}
点击即可实现跳转
2.使用useRouter
Hook
useRouter
Hook 可以在任何组件中访问路由相关的对象和方法,例如查询参数、路径名,以及进行编程式导航:
javascript
import { useRouter } from 'next/router';
import React from 'react';
export default () => {
const router = useRouter();
const goTo = (path) => {
router.push(`/${path}`);
}
return <>
<span onClick={() => { goTo('a') }}>a</span>
<br/>
<span onClick={() => { goTo('b/c') }}>b/c</span>
<br/>
<span onClick={() => { goTo('b') }}>b</span>
</>
}
Next如何使用路由守卫?
前面展示的路由访问例子,只要是文件系统下的组件,都可以直接通过路径访问到。但是有些时候我们希望拦截住一些路由进行重定向,或者放过一些路由。
比如,未登陆的用户,在访问index时,我们希望将之拦截,重定向到login界面。
这里我们建立login页面。
javascript
import React from 'react';
export default () => {
return <>login Page</>;
}
1.使用getServerSideProps
在服务端重定向
我们先介绍一下getServerSideProps
这个next.js提供的API:
getServerSideProps
是 Next.js 提供的一个 API,用于在服务器端渲染页面之前获取数据。这个 API 允许你在每次请求页面时从服务器端获取最新的数据,并将这些数据传递给页面组件。
参数
getServerSideProps
接收一个上下文对象(context
)作为参数,该对象包含以下属性:
params
: 一个包含动态路由参数的对象。如果页面使用了动态路由(例如[id].js
),则这些参数将包含在params
中。req
: Next.js 的req
对象,它代表 HTTP 请求。你可以通过这个对象访问请求头、查询参数、cookies 等。res
: Next.js 的res
对象,它代表 HTTP 响应。虽然你通常不需要直接操作这个对象(因为 Next.js 会自动处理响应),但在某些情况下,你可能需要用它来设置 cookies 或响应头。query
: 一个包含 URL 查询参数的对象。这允许你轻松访问 URL 中的查询字符串参数。resolvedUrl
: 一个字符串,表示页面的完整 URL(包括查询参数)。
返回值
getServerSideProps
应该返回一个对象,该对象可以包含以下属性:
props
: 一个对象,包含你想要传递给页面组件的属性(数据)。这些属性将在服务器端渲染时被注入到页面组件中。notFound
: 一个布尔值,表示页面是否应该返回 404 状态码。如果设置为true
,Next.js 将自动渲染 404 页面。redirect
: 一个对象,用于执行服务器端重定向。这个对象应该包含两个属性:destination
(重定向的目标 URL)和permanent
(一个布尔值,表示重定向是否是永久性的,默认为false
)。
所以getServerSideProps
可以在页面组件加载之前运行服务器端的逻辑,根据条件进行重定向。
由于我们没有登陆cookies,所以每次访问index的时候都会被重定向到login页面。
javascript
// pages/index.js
import React from 'react';
import Nav from '../components/Nav/nav.jsx';
export const getServerSideProps = async ({ req, res }) => {
// 检查用户是否登录,例如通过 cookies 或 headers
const isAuthenticated = req.cookies.authToken; // 只是一个示例
if (!isAuthenticated) {
// 重定向到登录页面
return {
redirect: {
destination: '/login',
permanent: false, // 通常设置为 false,除非你想进行永久重定向
},
};
}
return { props: {} };
};
export default () => {
return <Nav/>
}
2.使用 useEffect
和 router
在客户端进行重定向
在客户端,可以使用 React 的 useEffect
钩子和 Next.js 提供的 useRouter
钩子来实现路由守卫。
首先,我们创建一个名为useAuth
的权限hooks
javascript
// hooks/useAuth.js
export const useAuth = () => {
return {
isLogin: false, // 这里暂时默认未登陆
}
}
创建一个protect
页面,当我们访问protect页面时,就会被重定向到login页面。当isLogin被改为true时,就正常渲染。
javascript
// pages/protect.js
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '../hooks/useAuth'; // 假设你有一个自定义的 auth hook
const ProtectPage = () => {
const router = useRouter();
const { isLogin } = useAuth(); // 假设 useAuth 返回一个 isLogin 状态
useEffect(() => {
if (!isLogin) {
// 如果用户未认证,重定向到登录页面
router.push('/login');
}
}, [isLogin, router]);
return <div>This is a protect page.</div>;
};
export default ProtectPage;
3.创建一个高阶组件(HOC)
实际上,如果每个需要权限认证的页面都写一遍重定向逻辑是很麻烦的。这里我们就可以通过HOC简化统一这个逻辑。
这里我们创建一个HOC,用于检查是否有权限。
javascript
// hoc/withAuthRouter.jsx
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '../hooks/useAuth'; // 假设你有一个自定义的 auth hook
const withAuthRouter = (WrappedComponent) => {
return (props) => {
const router = useRouter();
const { isLogin } = useAuth(); // 假设 useAuth 返回一个 isLogin 状态
useEffect(() => {
if (!isLogin) {
// 如果用户未认证,重定向到登录页面
router.push('/login');
}
}, [isLogin, router]);
return <WrappedComponent {...props}/>
}
};
export default withAuthRouter;
我们再创建一个protectB
页面,包裹上HOC。
javascript
// pages/protectB.js
import React from 'react';
import withAuthRouter from "../hoc/withAuthRouter";
export default withAuthRouter(() => {
return <>I'm protectB</>;
});
这种方式很适合,少数几个页面有统一的路由逻辑的情况使用。但是HOC最好不要滥用,不然后期代码会难以维护。
4.在入口文件处统一拦截
最后这种方式就是将拦截处理统一在入口文件_app.js
当中,在next.js的页面访问逻辑当中,会先访问_app.js
再访问index.js
。
而_app.js
是应用级的,而index.js
是页面级的,所以通常将处理放在_app.js
当中。
下面的代码中,我们对不在白名单内的页面进行了重定向处理。
javascript
// _app.js
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
export default(context) => {
const { Component, pageProps } = context;
const router = useRouter();
useEffect(() => {
if(!WHITE_LIST.includes(router.pathname)) router.push('login');
}, []);
return <Component {...pageProps}/>
};
// 白名单,只能访问到以下页面
const WHITE_LIST = [
'/a',
'/b/c',
'/login',
'/protect',
]
上面这样做的好处在于可以进行全局处理,将路由的操作进行统一处理,代码更易于维护。
但是有些较为局部的处理,也集中到这里的话,会使代码变得臃肿,难以维护。所以还需要根据情况去选择合适的处理方式。