文章
Nextjs 问题记录
1、server端使用redirect函数跳转
createWorkflow.ts
"use server"
import prisma from "@/lib/prisma";
import { createWorkflowSchema, createWorkflowSchemaType } from "@/schema/workflow";
import { WorkflowStatus } from "@/types/workflow";
import { redirect } from "next/navigation";
export async function CreateWorkflow(form: createWorkflowSchemaType) {
const {success, data} = createWorkflowSchema.safeParse(form)
if (!success) {
throw new Error("invalid form data")
}
// const {userId} = auth()
// if (!userId) {
// throw new Error("unauthenticated")
// }
const userId = "test"
const result = await prisma.workflow.create({
data: {
userId,
status: WorkflowStatus.DRAFT,
definition: "TODO",
...data
}
})
if (!result) {
throw new Error("failed to create workflow")
}
redirect(`/workflow/editor/${result.id}`)
}
client端使用 useMutation捕获server端结果
...
const { mutate, isPending } = useMutation({
mutationFn: CreateWorkflow,
onSuccess: () => {
// 正常情况下不会执行,因为服务端 redirect 会触发 onError
},
onError: (error) => {
if (error instanceof Error && error.message.includes("NEXT_REDIRECT")) {
toast.success("Workflow created", { id: "create-workflow" })
} else {
// 真正的错误
console.error("Create workflow error:", error);
toast.error("Failed to create workflow", { id: "create-workflow" });
}
}
})
...
关键点说明
redirect
的工作原理:- Next.js 的
redirect
实际上会抛出一个特殊的错误(包含NEXT_REDIRECT
) - 这个错误会被 React Query 的
onError
捕获
- Next.js 的
- 错误类型判断:
- 通过检查
error.message.includes("NEXT_REDIRECT")
区分是正常跳转还是真实错误 - 如果是跳转,显示成功提示并关闭对话框
- 通过检查
- 用户体验优化:
- 在
onError
中先显示成功 toast,再关闭对话框 - 真实错误会显示错误提示并记录日志
- 在
2、Record类型
export type ProductType = {
id: string | number;
name: string;
shortDescription: string;
description: string;
price: number;
sizes: string[];
colors: string[];
// Record<string, string> 是一个内置的工具类型 他表示一个对象,其所有键都是字符串类型,所有值都是也都是字符串类型
// 相当于 { [key: string]: string }
images: Record<string, string>;
}
- Record 是一个内置的工具类型
- 其所有键都是字符串类型,所有值都是也都是字符串类型
3、数组对象遍历
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
- 在使用
map()
方法渲染列表时,为每个元素提供一个唯一的key
属性是 React 中一个非常重要且必须遵循的最佳实践。 - 识别哪些项发生了变化:当列表发生变化(添加、删除、重新排序)时,React 可以通过 key 来高效地确定哪些元素需要更新、创建或销毁。
- 提高性能:如果没有 key,React 在更新列表时可能需要重新渲染更多的元素,甚至可能导致整个列表被重新渲染,这会降低性能。有了稳定的 key,React 可以实现更精确的差分算法(diffing algorithm),只更新必要的部分。
- 维持状态:对于包含状态(state)的列表项(如表单输入框),key 可以帮助 React 在列表顺序改变后,正确地将状态与对应的元素关联起来,避免状态错乱。
3、获取页面查询参数并修改其中一个参数,创建新的查询参数
这里的查询参数也就是URL中的?a=1&b=2这样的参数
const searchParams = useSearchParams();
// 获取URL中的PATH路径
const pathname = usePathname();
const selectedCategory = searchParams.get("category");
const handleChange = (value: string | null) => {
// 根据当前页面的searchParams创建URLSearchParams
const params = new URLSearchParams(searchParams);
// 修改当前searchParams中category参数,其他参数保持不变
params.set("category", value || "all");
// router.push(`/?category=${value}`);
// 使用searchParams生成URL查询链接
router.push(`${pathname}?${params.toString()}`, {scroll: false});
}
- 可以理解为动态的生成URL中的查询地址 比较经典的重新当前path路径重新组装query查询
- router.push()函数中使用的scroll: false 在分类切换的时候页面不会回到顶部,而是保持当前位置
- 获取查询参数:客户端组件使用useSearchParams()函数,page.tsx则使用export函数传参的形式export default async function HomePage({searchParams}: { searchParams: Promise<{ 参数名: string }> }),函数体中可以使用如下方式获取参数值:const category = (await searchParams).参数名;
4、useSate初始化一个对象多个参数
// 会提交product到购物车 需要存储 当前product选择的color、size参数
const [productTypes, setProductTypes] = useState({ // 这里商品状态存储的是一个对象,默认使用第一个size以及第一个color
color: product.colors[0],
size: product.sizes[0],
});
// 上面定义了当前商品的color、size的默认值
// 下面定义 修改商品的其中一个参数
const handleProductType = ({type, value}: { type: "color" | "size"; value: string }) => {
// 上面定义当前商品有 两个属性 color 和 size
// 使用prev传参 意思是保留之前的 参数对应的值 然后修改 当前type对应的值
// 比如选择一个尺寸的时候 再去选一个颜色,正确的是应该尺寸会发生改变 ,并记录当前的颜色,而不是尺寸变为默认值
setProductTypes((prev) => // prev 只是你给函数参数起的一个有意义的名字,React 会自动将当前状态值传递给它。
// prev 确实是"变化之前的值",但它是"当前最新的变化之前的值" [type]: value 是最新的变化
// ...prev - 展开操作符 相当于:{ color: prev.color, size: prev.size }
// [type]: value - 计算属性名,这里的方括号[]不是数组,而是计算属性名的语法
// [type]: value 意思是:使用变量type的值作为属性名
({...prev, [type]: value}));
}
- 这里要注重理解setProductTypes传参
修改size:
<select
name="size"
id="size"
className={"ring ring-gray-300 rounded-md px-2 py-1"}
// 点击size下拉框改变下拉框的值的时候 触发handleProductType函数 并传参
onChange={(e) => handleProductType({type: "size", value: e.target.value})}
>
{product.sizes.map((size) => (
<option key={size} value={size}>{size.toUpperCase()}</option>
))}
</select>
修改color:
// 点击商品的颜色圆圈 触发handleProductType函数并传参
<div key={color} className={""}
onClick={() => handleProductType({type: "color", value: color})}>
{/*画个带颜色的实心圆*/}
<div
className={"w-[14px] h-[14px] rounded-full"}
style={{backgroundColor: color}}
/>
</div>