跳到主要内容

修改 Issue

添加修改 Button

本节代码链接

安装 Radix UI 的 Radix Ui Icons

npm i @radix-ui/react-icons
/app/issues/[id]/page.tsx
  ...
const IssueDeatilPage = async ({ params }: Props) => {
...

return (
// 添加一个 Grid 以分列显示,设置 initial 为 1,在移动设备为每页 1 栏,平板以上则 2 栏
<Grid columns={{ initial: "1", md: "2" }} gap="5">
<Box>
<Heading as="h2">{issue.title}</Heading>
...
</Box>
{/*添加一个 Button 用于编辑*/}
+ <Box>
+ <Button>
+ <Pencil2Icon />
+ <Link href={`/issues/${issue.id}/edit`}>Edit Issue</Link>
+ </Button>
+ </Box>
</Grid>
);
};
export default IssueDeatilPage;

Single Responsbility Principle

本节代码链接

Software entities should have a single responsibility

重构 /app/issues/[id]/page.tsx 以应用 SRP

/app/issues/[id]/page.tsx
import prisma from "@/prisma/client";
import { Box, Grid } from "@radix-ui/themes";
import { notFound } from "next/navigation";
import EditIssueButton from "./EditIssueButton";
import IssueDetails from "./IssueDetails";

interface Props {
params: { id: string };
}
const IssueDeatilPage = async ({ params }: Props) => {
const issue = await prisma.issue.findUnique({
where: { id: parseInt(params.id) },
});

if (!issue) notFound();

return (
<Grid columns={{ initial: "1", md: "2" }} gap="5">
<Box>
<IssueDetails issue={issue} />
</Box>
<Box>
<EditIssueButton issueId={issue.id} />
</Box>
</Grid>
);
};
export default IssueDeatilPage;

修改 Issue

页面

本节代码链接

我们可以像这样构建文件结构,在 Issue 目录下创建 _components 以放置该目录下需要重复使用的组件,文件夹名前添加下划线就可以把这个文件夹从路由中移除

└─issues
│ IssueActions.tsx
│ loading.tsx
│ page.tsx

├─new
│ loading.tsx
│ page.tsx

├─[id]
│ │ EditIssueButton.tsx
│ │ IssueDetails.tsx
│ │ loading.tsx
│ │ page.tsx
│ │
│ └─Edit
│ page.tsx

└─_components
IssueForm.tsx

将之前的 new/page.tsx 封装为一个组件,并添加一个可选参数,以初始化

/app/issues/_components/IssueForm.tsx
  ...
+ import { Issue } from "@prisma/client";
...
// 添加一个可选参数 issue 类型为之前 prisma 中的 Issue
- const IssueForm = () => {
+ const IssueForm = ({ issue }: { issue?: Issue }) => {
...

return (
<div className="max-w-xl prose">
...
<TextField.Root>
<TextField.Input
// 将该字段初始化为 issue.title (若传入 issue)
+ defaultValue={issue?.title}
placeholder="Title"
{...register("title")}
/>
</TextField.Root>
<ErrorMessage>{errors.title?.message}</ErrorMessage>
<Controller
// 将该字段初始化为 issue.description (若传入 issue)
+ defaultValue={issue?.description}
name="description"
control={control}
render={({ field }) => (
<SimpleMDE placeholder="Description" {...field} />
)}
/>
...
</div>
);
};
export default IssueForm;

API

本节代码链接

/app/api/issues/[id]/route.tsx
import { issueSchema } from "@/app/validationSchema";
import { NextRequest, NextResponse } from "next/server";
import prisma from "@/prisma/client";

export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const body = await request.json();
const validation = issueSchema.safeParse(body);
if (!validation.success)
return NextResponse.json(validation.error.format(), { status: 400 });

const issue = await prisma.issue.findUnique({
where: { id: parseInt(params.id) },
});
if (!issue)
return NextResponse.json({ error: "Invalid Issue" }, { status: 404 });

const updatedIssue = await prisma.issue.update({
where: { id: issue.id },
data: { title: body.title, description: body.description },
});

return NextResponse.json(updatedIssue, { status: 200 });
}

连接

本节代码链接

/app/issues/_components/IssueForm.tsx
  const IssueForm = ({ issue }: { issue?: Issue }) => {
...

return (
...
<form
className="space-y-3"
onSubmit={handleSubmit(async (data) => {
try {
setSubmitting(true);
// 判断是否传入了 issue,若有传入则是 Update,若无则是 new
+ if (issue) await axios.patch("/api/issues/" + issue.id, data);
- await axios.post("/api/issues", data);
+ else await axios.post("/api/issues", data);
router.push("/issues");
} ...
})}
>
...
<Button disabled={isSubmitting}>
+ {issue ? "Update Issue" : "Submit New Issue"}{" "}
{isSubmitting && <Spinner />}
</Button>
</form>
...
);
};
export default IssueForm;

Caching

本节代码链接

NextJS Route Segment Config

  • Data Cache:
    • When we fetch data using fetch()
    • Stored in the file system
    • Permanent unitl we redeploy
    • fetch(".",{cache: "no-store"})
    • fetch(".",{revalidata: 3600})
  • Full Route Cache
    • Used to store the output of statically renderd routes
  • Router Cache (Client-side Cache)
    • To store the payload of pages in browser
    • Lasts for a session
    • Gets refreshed when we reload

提升 Loading 体验

本节代码链接

由于我们要在多个地方用到 IssueForm 的 Skeleton,我们可以将其封装到一个组件里,然后在需要的地方调用。其次,对于静态的页面可以直接使用 loading.tsx,但是对于需要用到 dynamic 函数的页面,应该用另一种方法

/app/issues/_components/IssueFormSkeleton.tsx
import { Skeleton } from "@/app/components";
import { Box } from "@radix-ui/themes";

const IssueFormSkeleton = () => {
return (
<Box className="max-w-xl">
<Skeleton height="2rem" />
<Skeleton height="20rem" />
</Box>
);
};
export default IssueFormSkeleton;
请作者喝可乐🥤: