我试图了解我们在 Next.js 应用程序 v.14(应用程序路由器)中遇到的问题。
我们有一个 SSR 页面,它首先通过调用验证令牌端点来验证 JWT 令牌。如果令牌有效,我们会在此页面上呈现一个表单(use-client 指令)。该表单使用客户端操作来验证用户输入。验证成功后,它会触发服务器端操作,该操作与 API 通信以注册用户。
我们看到的问题是,成功提交表单后,会再次调用 verifySignupToken。由于我们在使用 router.push 提交表单后进行客户端导航,我们应该直接跳到新路线吗?我不确定为什么它会在父组件中再次重新触发 fetch 调用?
SSR 页面(带有强制动态配置选项):
export const dynamic = 'force-dynamic'
export default async function Signup({ searchParams }: SignUpProps) {
const { token, customerId, email } = searchParams
const result = safeParse(VerifySignupSchema, { token, email, customerId })
if (!result.success) {
console.error(result.issues)
redirect('/login?error=validation')
}
const validatedData = result.output
try {
await verifySignupToken(validatedData.token, validatedData.customerId, validatedData.email)
} catch (error) {
if (error instanceof Error) {
redirect(`/login?error=${encodeURIComponent(error.message)}`)
}
}
return (
<SignUpForm
token={validatedData.token}
customerId={validatedData.customerId}
email={validatedData.email}
/>
)
}
SignUpForm(使用客户端指令,客户端操作调用服务器端操作):
export const SignUpForm = ({ token, customerId, email }: SignUpFormProps) => {
const router = useRouter()
const { pending } = useFormStatus()
const signUpClientAction = async (formData: FormData) => {
const password = formData.get('password')
const pnr = formData.get('pnr')
const result = safeParse(SignUpSchema, { password, pnr })
if (!result.success) {
toast.error(result.issues[0].message)
return
}
const validatedData = result.output
const response = await signupAction(
email,
validatedData.password,
token,
validatedData.pnr,
customerId
)
if (response.success) {
toast.success(response.message)
router.push('/')
} else if (response.statusCode === 401) {
toast.error(response.message)
router.push('/login')
} else {
toast.error(response.message)
}
}
return (
<div className="mt-10">
<div>
<form className="space-y-6" action={signUpClientAction}>
....
在 next.js 13 之前,next.js 有
shallow routing
,这是一种允许您更新页面 URL 的技术,而无需重新加载页面本身或从服务器获取新数据。
来自 shallow-routing-with-next-js-v13-app-directory-
在 Next.js v13 中,总是添加或删除搜索参数 触发硬重载。相比之下,Next.js v12 允许命令 像 router.push('/?counter=10', undefined, {shallow: true }) 一样 在不启动硬重新加载的情况下执行。不幸的是,在 当前版本,类似的代码总是会导致硬重新加载,从而导致 所有组件都需要重新渲染,即使是同一页面。
解决方案
当新实现的应用程序面临此类限制时,重新访问 Web API 总是一个好主意。我们的 这里的目标是找到一种无需立即修改 URL 的方法 触发整页重新加载。
实现此目的的一种流行方法是使用 “window.history.pushState()”。
现在我需要监听另一个 URL 的变化 成分。那么让我们编写一个钩子来为我们做到这一点。
import { useEffect } from 'react';
export const usePushStateListener = (callback) => {
useEffect(() => {
// make a copy of original function to avoid complications
const originalPushState = history.pushState;
history.pushState = function (data, title, url) {
originalPushState.apply(history, [data, title, url]);
callback(url);
return () => {
history.pushState = originalPushState; // restore the copy
};
}, [callback]);
};
当在任何情况下调用“window.history.pushState”方法时 组件中定义的重写的“pushState”方法 “usePushStateListener”钩子将被执行。这允许钩子 监听 URL 变化并触发更新后的回调函数 网址。