【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

相关推荐
crary,记忆7 小时前
微前端 - Module Federation使用完整示例
前端·react·angular
aiguangyuan14 小时前
浅谈 React Hooks
react·前端开发
whatever who cares2 天前
React hook之userReducer
react.js·react
aiguangyuan2 天前
React Hooks 基础指南
react·前端开发
aiguangyuan3 天前
React 项目初始化与搭建指南
react·前端开发
aiguangyuan4 天前
React 组件异常捕获机制详解
react·前端开发
aiguangyuan4 天前
深入理解 JSX:React 的核心语法
react·前端开发
aiguangyuan4 天前
React 基础语法
react·前端开发
山水域4 天前
0基础入门AI编程之环境篇Devbox
next.js
aiguangyuan6 天前
React 核心概念与生态系统
react·前端开发