【Next.js 项目实战系列】04-修改 Issue

原文链接

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的点个star,关注一下吧

上一篇【Next.js 项目实战系列】03-查看 Issue

修改 Issue

添加修改 Button

本节代码链接

安装 Radix UI 的 Radix Ui Icons

npm i @radix-ui/react-icons
TypeScript 复制代码
# /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

  • page.tsx
  • IssueDetails.tsx
  • EditIssueButton.tsx
TypeScript 复制代码
# /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;
TypeScript 复制代码
# /app/issues/[id]/IssueDetails.tsx

import { IssueStatusBadge } from "@/app/components";
import { Issue } from "@prisma/client";
import { Card, Flex, Heading, Text } from "@radix-ui/themes";
import ReactMarkdown from "react-markdown";

const IssueDetails = ({ issue }: { issue: Issue }) => {
  return (
    <>
      <Heading as="h2">{issue.title}</Heading>
      <Flex gap="3" my="5">
        <IssueStatusBadge status={issue.status}></IssueStatusBadge>
        <Text>{issue.createdAt.toDateString()}</Text>
      </Flex>
      <Card className="prose">
        <ReactMarkdown>{issue.description}</ReactMarkdown>
      </Card>
    </>
  );
};
export default IssueDetails;
TypeScript 复制代码
# /app/issues/[id]/EditIssueButton.tsx

import { Pencil2Icon } from "@radix-ui/react-icons";
import { Button } from "@radix-ui/themes";
import Link from "next/link";

const EditIssueButton = ({ issueId }: { issueId: number }) => {
  return (
    <Button>
      <Pencil2Icon />
      <Link href={`/issues/${issueId}/edit`}>Edit Issue</Link>
    </Button>
  );
};
export default EditIssueButton;

修改 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 封装为一个组件,并添加一个可选参数,以初始化

TypeScript 复制代码
# /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

本节代码链接

TypeScript 复制代码
# /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 });
}

连接

本节代码链接

TypeScript 复制代码
# /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 函数的页面,应该用另一种方法

  • IssueFormSkeleton.tsx
  • page.tsx
  • loading.tsx
TypeScript 复制代码
# /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;
TypeScript 复制代码
# /app/issues/[id]/edit/page.tsx

import prisma from "@/prisma/client";
import dynamic from "next/dynamic";
import { notFound } from "next/navigation";
import IssueFormSkeleton from "./loading";

const IssueForm = dynamic(() => import("@/app/issues/_components/IssueForm"), {
  ssr: false,
  loading: () => <IssueFormSkeleton />,
});

interface Props {
  params: { id: string };
}

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

  if (!issue) notFound();

  return <IssueForm issue={issue} />;
};
export default EditIssuePage;
TypeScript 复制代码
# /app/issues/[id]/edit/loading.tsx

import IssueFormSkeleton from "@/app/issues/_components/IssueFormSkeleton";
export default IssueFormSkeleton;

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的点个star,关注一下吧

下一篇讲删除 Issue

下一篇【Next.js 项目实战系列】05-删除 Issue

相关推荐
一叶茶2 天前
前端生成docx文档、excel表格、图片、pdf文件
前端·javascript·react
走,板砖去4 天前
大文件传输与断点续传实现(极简Demo: React+Node.js)
node.js·react
SRestia4 天前
Django+React---从0搭建一个听音乐+聊天室的网站
websocket·django·react
远洋录4 天前
前端部署实战:从人工发布到全自动化流程
前端·人工智能·react
红绿鲤鱼5 天前
Next.js v15 - 服务器操作以及调用原理
前端·next.js
Rudon滨海渔村6 天前
React-antd组件库 - 让Menu子菜单项水平排列 - 下拉菜单排序 - 自定义子菜单展示方式
前端·react.js·前端框架·react
远洋录8 天前
前端单元测试实战:从零开始构建可靠的测试体系
前端·人工智能·react
Krorainas8 天前
将PDF流使用 canvas 绘制然后转为图片展示在页面上(二)
前端·javascript·pdf·react
跟着影子奔跑8 天前
博客MDX渲染方案
next.js
远洋录9 天前
前端性能优化实战:从加载到渲染的全链路提升
前端·人工智能·react