230516.til(next-js)

Universe·2023년 5월 16일
0
post-custom-banner

서론

가끔은 sudo 키워드를 인생에도 적용할 수 있으면 좋겠다는 생각을 한다.
의지와 상관없이 거부할 수 없는 명령같은거.
이상한 생각 금지.
최근들어 벨로그가 자주 먹통이 되는게 참 안타깝다.

Update

넥스트 CRUD 세번째.
업데이트 로직은 리액트에서 업데이트 자체가 어렵다기 보다는
업데이트 레이아웃을 컨트롤 하는 부분이 어려웠던 것 같다.
사실 CRUD api 로직은 전부 비슷비슷해서
명령을 보낸다 -> 수행한다 의 로직을 따르는데 (프로그래밍이 대부분 이렇지만)
리액트에서의 업데이트는 기존의 데이터를 불러와서 바인딩 하고,
그 데이터를 수정하고 업데이트 로직을 수행할 때 기존의 데이터를 어떤식으로 업데이트 할 지.
이 말로는 간단해보이는 로직이 정말 수많은 방법이 있고,
예외사항이나 (예를들어 이미지나 캔버스) 시행착오도 많이 겪었던 것 같다.
넥스트는 어떻게 다른지 알아보자.

//update/[postId]/page.js
import postCollectionAPI from "@/util/postCollectionAPI";
import { ObjectId } from "mongodb";

export default async function Update({ params }) {
  const data = await postCollectionAPI("findOne", {
    _id: new ObjectId(params.postId),
  });

  return (
    <div>
      <h4>수정페이지</h4>
      <form action="/api/update" method="POST">
        <input name="title" defaultValue={data.title} />
        <input name="content" defaultValue={data.content} />
        <input
          style={{ display: "none" }}
          name="_id"
          defaultValue={data._id.toString()}
        />
        <button type="submit">수정하기</button>
      </form>
    </div>
  );
}

list 페이지에서 버튼을 누르면 해당 페이지로 이동한다.
post 페이지와 동일한 ui 에 서버 컴포넌트로 데이터를 바인딩했다.
_id 값은 유동적으로 변해야 하기때문에 dynamic route 를 적용했다.
params.postId 에 _id 값을 담은 컴포넌트.
mongo db 의 고유 id는 new ObjectId 형식이기 때문에 저런식으로 바인딩 해줘야 한다.
주의할 점은 form elements 는 DELETE, UPDATE 등의 method 를 인식할 수 없으므로
POST 메소드로 설정해야 한다는 점.
굉장히 보기에 불편하지 않을 수 없다.
그리고 submit 되었을 때 서버에서 id 로 검색을 해야 하기 때문에
_id 값을 input 에 담아서 같이 보내주고 화면에는 보이지 않게 display : none 을
설정해준 모습.

import { ObjectId } from "mongodb";
import { connectDB } from "./db";

export default async function postCollectionAPI(
  method,
  query = {},
  options = {}
) {
  const client = await connectDB;
  const db = client.db("forum");
  const postCollection = db.collection("post");
  let result;

  switch (method) {
    case "find":
      result = postCollection.find(query, options).toArray();
      break;
    case "findOne":
      result = postCollection.findOne(query, options);
      break;
    case "insertOne":
      postCollection.insertOne(query);
      break;
    case "updatePost":
      const { _id, title, content } = query;
      postCollection.updateOne(
        { _id: new ObjectId(_id) },
        { $set: { title, content } }
      );
      break;
    default:
      throw new Error("잘못된 요청입니다.");
  }

  return result;
}

이제 postCollectionAPI 함수에 updatePost case 를 추가해줬다.
사실, 해당 함수의 method case 를 find, findOne 처럼 그대로 해버리면
함수의 확장성이 떨어진다.
연습삼아 만든 api 라서 그대로 차용하고 있지만
update 기능은 차후에 확장가능성이 다분하므로 updatePost 로 명명해줬다.
이런 패턴이 옳은 패턴인지는 다른 개발자분들의 프로젝트를 봐야겠지만,
현재까지는 크게 나쁘다고 생각하지 않아 적용하고 있다.
다른 개발자분들이라고 해서

  const client = await connectDB;
  const db = client.db("forum");
  const postCollection = db.collection("post");

이런 코드가 모든 컴포넌트에 존재하는걸 좋아할 리가 없으므로.

mongo DB의 updateOne 메소드는 특정조건을 설정해 해당 조건을 만족하는 데이터 중
가장 먼저 찾을 수 있는 데이터를 가져와 원하는 값으로 수정해준다.
첫번째 인자에 조건, $set key 값의 value 가 수정할 내용이 된다.
위의 조건은 id로 게시물을 찾는 예시.

// pages/api/update.js
import postCollectionAPI from "@/util/postCollectionAPI";

export default function update(req, res) {
  if (req.method === "POST") {
    const { _id, title, content } = req.body;
    postCollectionAPI("updatePost", { _id, title, content });

    res.redirect(302, "/list");
  }
}

그러면 api 코드에서 전달받은 데이터를 알맞게 넣어주기만 하면 완성.
응답으로 list 페이지로 redirect 될 수 있도록 해줬다.
연습용이므로 크게 예외처리를 신경쓰지는 않았지만
try catch 문을 이용해서 예외처리를 해주는 것이 좋다.




Delete

delete 기능은 훨씬 간단하다.

"use client";

import { useRouter } from "next/navigation";
import DetailLink from "./DetailLink";
import UpdateLink from "./UpdateLink";

export default function Listitem({ listitem }) {
  const handlePostDelete = (id) => {
    fetch(`/api/remove?id=${id}`, {
      method: "DELETE",
    })
  return (
    <>
      {listitem.map((item) => (
        <div className="list-item" key={item._id}>
          <DetailLink postId={item._id.toString()}>
            <h4>{item.title}</h4>
            <p>{item.date}</p>
          </DetailLink>
          <UpdateLink postId={item._id.toString()} />
          <button onClick={() => handlePostDelete(item._id.toString())}>
            삭제하기
          </button>
        </div>
      ))}
    </>
  );
}

fetch 등의 기능은 서버 컴포넌트에서 사용할 수 없으니까
클라이언트 컴포넌트로 listItem 을 따로 만들어줬다.
위의 컴포넌트는 list 서버 컴포넌트에서 props 로 데이터를 받아 바인딩하고,
버튼에 id를 담아 handlePostDelete 함수로 삭제기능을 구현한 예시.

가장 주의해야 할 점.
"DELETE" 메소드는 body 를 사용하지 않는 것이 좋다.
대부분의 HTTP 클라이언트가 DELETE 메소드에 body 를 포함하는 것을 허용하지 않는다.
따라서 쿼리 스트링이나 URL 파라미터로 보내는 방법을 선택했다.

`/api/remove?id=${id}`

이런 식으로 ? 기호 뒤에 key=value 형식으로 넣어주면 된다.
이렇게 보낸 쿼리 스트링은

import postCollectionAPI from "@/util/postCollectionAPI";

export default async function remove(req, res) {
  if (req.method === "DELETE") {
    await postCollectionAPI("deleteOne", req.query.id);
    res.status(200).json();
  }
}

req.query 메소드로 받아올 수 있다.
body 와 query 의 차이에 주의할 것.

import { ObjectId } from "mongodb";
import { connectDB } from "./db";

export default async function postCollectionAPI(
  method,
  query = {},
  options = {}
) {
  const client = await connectDB;
  const db = client.db("forum");
  const postCollection = db.collection("post");
  let result;

  switch (method) {
    case "find":
      result = postCollection.find(query, options).toArray();
      break;
    case "findOne":
      result = postCollection.findOne(query, options);
      break;
    case "insertOne":
      postCollection.insertOne(query);
      break;
    case "updatePost":
      const { _id, title, content } = query;
      postCollection.updateOne(
        { _id: new ObjectId(_id) },
        { $set: { title, content } }
      );
      break;
    case "deleteOne":
      postCollection.deleteOne({ _id: new ObjectId(query) });
      break;
    default:
      throw new Error("잘못된 요청입니다.");
  }

  return result;
}

deleteOne 메소도 위의 update 와 비슷하게 조건을 설정해 해당 조건을 만족하는
데이터를 삭제하면 된다.

여담

넥스트를 공부하다 보면
새로운 기술을 배운다! 라는 느낌보다는
리액트를 구조적으로 어떻게 설계하면 조금 더 효율적인 컴포넌트를 만들 수 있다!
를 배워나가는 것 같다.
물론 추가된 멋지고 유용한 기능들도 많지만
디렉토리 기반 라우팅 같은 것들로 미루어보아 조금 더 리액트를 우아하게 다룰 수 있도록
해준다는 느낌이 강하다.
서버 쪽 코드는 지금과 같이 얕은 수준에서는 크게 어려움이 없는 수준이고
오히려 직접 설계할 수 있으니까 구조적으로 조금 더 생각하게 되는 느낌.

profile
Always, we are friend 🧡
post-custom-banner

0개의 댓글