코딩/React

Remix Framework Action 함수 자세히 알아보기

드리프트 2022. 3. 26. 17:55
728x170
Remix framework action function, 리믹스 프레임워크 action 함수 자세히 알아보기

 

 

안녕하세요?

 

지난 시간까지 배웠던 Remix 프레임워크의 서버사이드 담당 함수인 loader와 action 중에서 오늘은 action 함수를 좀 더 자세히 알아볼까 합니다.

 

지난 시간에 배웠던 loader, action 함수의 동작 방식을 복습해 보면,

 

1. 리액트 UI 부분의 form 엘러먼트에서 디폴트 옵션인 method="get" 상태에서 form을 submit 했고,

 

2. form을 submit 하면 브라우저는 자동으로 새로고침 하는데, 이때 form의 method가 "get"이기 때문에 input 엘레먼트의 name 부분이 현재 URL의 Params으로 추가되면서,

 

3. 새로고침 된 브라우저는 URL Params이 붙은 상태이고, 이때 loader 함수에서 TMDB API를 이용해서 params의 값을 filter 해서 원하는 영화 정보만 추출했습니다.

 

4. 그리고 최종적으로 loader에서 리턴된 data를 useLoaderData() 훅을 이용해서 다시 리액트 UI 쪽에 뿌려주는 역할을 했습니다.

 

그리고 지난 시간 마지막에 form method="post" 방식으로도 잠깐 소개했었는데, 이번 시간에는 본격적인 form method="post" 방식의 action 함수를 다뤄볼 생각입니다.

 

form의 제출과 관련해서 action 함수가 하는 가장 보편적인 일은 바로 DB 관련 일입니다.

 

그래서 Posts 생성과 관련된 작업을 예로 들어 설명해 보겠습니다.

 

먼저, DB 작업을 위해 예전에 소개했던 Prisma를 쓸 예정입니다.

 

먼저, Prisma 설치를 위해 아래와 같이 실행시킵시다.

 

npm i prisma @prisma/client

 

이제, prisma를 초기화시켜줘야 하는데요.

 

npx prisma init

 

이렇게 하면 프로젝트 최상단에 prisma 폴더가 생기고 거기에 schema.prisma 파일이 생깁니다.

 

이제 schema.prisma 파일을 아래와 같이 수정합시다.

 

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = "file:dev.db"
}

model Post {
  id        String   @id @default(uuid())
  title     String
  body      String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

 

테스트를 위해 sqlite를 쓸 거고, db 위치는 로컬이며 dev.db란 이름으로 지정했습니다.

 

그리고 Post 모델을 지정했는데요.

 

id, title, body, createdAt, updatedAt 등 가장 일반적인 방식으로 지정했습니다.

 

이제 Prisma 스키마를 만들었으니까 이걸 코드에 적용해야겠죠.

 

다음과 같이 db push 하시면 됩니다.

 

npx prisma db push

위와 같이 실행하면 prisma 폴더에 dev.db 파일이 생길 겁니다.

 

npx prisma studio

그리고 prisma studio를 실행시켜 웹브라우저상에서 DB 상태를 실시간으로 확인할 수 있습니다.

 

 

이제, Prisma를 우리의 Remix 코드에 불러오기 위한 전역 Helper Util을 만들어야 합니다.

 

어디서든 간단하게 불러올 수 있게 말이죠.

 

먼저, Helper Utils 폴더를 만들어야겠죠.

 

리믹스 안에서 사용할 거라서 app 폴더 밑에 만들어야 합니다.

 

app/routes 폴더 밑에 만들면 file-base routing 이 되기 때문에 여기에는 만들면 안 됩니다.

 

그래서 app/utils 폴더를 만들고 그 밑에 db.server.ts 파일을 만들도록 합시다.

 

db.ts라고 이름 짓지 않고 db.server.ts라고 이름 지은 이유는. server라고 명시하면 리믹스 프레임워크는 이 파일을 무조건 서버사이드에서만 실행시키게 합니다.

 

참고 바랍니다.

 

db.server.ts 파일입니다.

 

import { PrismaClient } from "@prisma/client";

let db: PrismaClient;

declare global {
    var __db: PrismaClient | undefined;
}

if (process.env.NODE_ENV === 'production') {
    db = new PrismaClient();
    db.$connect();
} else {
    if (!global.__db) {
        global.__db = new PrismaClient();
        global.__db.$connect();
    }
    db = global.__db;
}

export { db }

 

이제 이 파일만 import 하면 리믹스 코드 어디에서든지 Prisma DB에 접근할 수 있습니다.

 

이제, 본격적인 Posts 구현에 들어가 볼까요?

 

먼저, app/routes/posts 폴더를 만들고 index.tsx파일을 생성합시다.

 

export default function PostsIndex() {
  return <div>PostsIndex</div>;
}

라우팅이 잘 되는지 브라우 저상에서 볼까요?

 

당연히 아까 prisma를 위해 껐던 개발서버를 "npm run dev"로 실행시켜야겠죠!

 

라우팅은 잘 되고 있습니다.

 

먼저, PostsIndex 라우팅에는 Post 리스트를 보여줘야 하는데 우리는 아직까지 DATA가 없습니다.

 

그래서 개발할 때는 dummy Data를 집어넣는데요.

 

아까 실행한 prisma studio를 이용해서 몇 개 집어넣어 봅시다.

Add record를 눌러서 post 아이템을 추가하고 title과 body부분에만 더미 데이터를 입력합시다.

 

나머지는 자동으로 부여되기 때문에 건들지 마시고요.

 

마지막으로 위에 "Save 1 change" 버튼을 눌러 저장하시면 됩니다.

 

만드는 김에 여러 개 만듭시다.

 

이제 /app/routes/posts/index.tsx 파일을 다음과 같이 만듭시다.

 

import { Link, LoaderFunction, useLoaderData } from "remix";
import { db } from "../../utils/db.server";

export const loader: LoaderFunction = async () => {
  const data = {
    posts: await db.post.findMany({
      take: 5,
      select: { id: true, title: true, createdAt: true },
      orderBy: { createdAt: "desc" },
    }),
  };

  return data;
};

export default function PostsIndex() {
  const { posts } = useLoaderData();

  return (
    <div className="ml-10">
      <h1>Posts</h1>
      <Link to="/posts/new">New Post</Link>
      <div>
        <ul>
          {posts.map((post) => (
            <li key={post.id} className="border w-48">
              <Link to={post.id}>
                <h3>{post.title}</h3>
                {new Date(post.createdAt).toLocaleDateString()}
              </Link>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

UI 부분을 신경 안 쓰고 순전히 로직만 신경 쓰기 위해 Tailwind를 지정 안 했더니 위와 같이 못생긴 UI가 나왔네요.

 

코드를 잘 보시면 New Post는 app/routes/posts/new라는 라우팅으로 이동하는 거고, 당연히 이 링크는 새 post를 작성할 때 쓰입니다.

 

그리고, <Link to={post.id}>는 해당 포스트의 아이디를 이용한 다이내믹 라우팅으로 이동하는 겁니다.

 

위 코드에서는 db.post.findMany 부분에 take: 5로 지정해서 5개만 가져오도록 했는데,

 

take 부분을 지우던가 아니면 take: 20으로 바꿔서 전체를 가져오도록 해보겠습니다.

 

그리고 Prisma Studio에서 신규 생성한 포스트가 잘 반영되는지 체크해 봅시다.

 

여기서 유의하셔야 할게 브라우저에서 새로 고침 해야 loader 함수가 다시 실행되는 점 유의하시기 바랍니다.

 

이제, 잘 생성됐으니까 오늘의 주제인 action 함수를 위한 app/routes/posts/new.tsx 파일을 만들어 보도록 합시다.

 

export default function NewPost() {
  return <div>Add New Post</div>;
}

라우팅은 잘 작동되고 있습니다.

 

이제 form 부분과 action 함수를 작성해 볼까요?

 

먼저, UI 부분입니다.

 

import { Link } from "remix";

export default function NewPost() {
  return (
    <div>
      <h1>Add New Post</h1>
      <Link to="/posts" className="bg-yellow-500">
        Goto Posts Lists
      </Link>
      <form method="post" className="grid grid-cols-1 w-1/3">
        <label htmlFor="title">Title</label>
        <input className="border" type="text" name="title" id="title" />
        <label htmlFor="body">Body</label>
        <textarea className="border" name="body" id="body" />
        <button className="bg-blue-300" type="submit">
          Add Post
        </button>
      </form>
    </div>
  );
}

기본적인 CSS만 적용했습니다.

 

이제 form method="post" 일 때 작동하는 action 함수를 작성해야겠죠.

 

import { ActionFunction, Link, redirect } from "remix";

export const action: ActionFunction = async ({ request }) => {
  const form = await request.formData();
  const title = form.get("title");
  const body = form.get("body");
  const fields = { title, body };

  console.log(fields);

  return redirect("/posts");
};

 

이제 테스트를 위해 form에 아무 글자나 넣고 submit 버튼을 눌러볼까요?

 

브라우저는 아마 posts 라우팅으로 갈 거고, 아마 서버사이드 쪽 터미널에 아래와 같이 나타날 겁니다.

 

title과 body 값이 제가 입력한 값과 동일하게 서버사이드로 잘 전달되었네요.

 

이제 action 함수를 prisma db에 저장하는 코드로 바꿔 보겠습니다.

 

import { ActionFunction, Link, redirect } from "remix";
import { db } from "~/utils/db.server";

export const action: ActionFunction = async ({ request }) => {
  const form = await request.formData();
  const title = form.get("title");
  const body = form.get("body");
  const fields = { title, body };
  //   console.log(fields);
  const post = await db.post.create({
    data: fields,
  });

Prisma가 좋은 점은 Prisma를 이용할 경우 Prisma의 DB 사용법이 일관되기 때문에 DB를 sqlite3나, postgreSQL을 쓰나 mySQL을 쓰나 Prisma 작동 코드는 동일하기 때문에 최근에 각광받고 있는 라이브러리입니다.

 

이제 테스트해볼까요?

 

 

777777로 입력한 title부분이 DB에 아주 잘 적용되고 있습니다.

 

이렇듯 리믹스의 action 함수는 form 엘러먼트가 method="post"일 경우 사용할 수 있고 주로 DB 부분을 관할하게 됩니다.

 

action 함수에 대해 알아보기 위해 Post 생성과 Post리스트 보여주기까지 코드를 작성했는데요.

 

이왕 Post 상세 리스트를 다이내믹 라우팅으로 작성해 보겠습니다.

 

app/routes/posts/$postId.tsx 파일이 우리가 원하는 다이내믹 라우팅의 파일 이름입니다.

 

import { LoaderFunction, useLoaderData } from "remix";
import { db } from "~/utils/db.server";

export const loader: LoaderFunction = async ({ params }) => {
  const post = await db.post.findUnique({
    where: {
      id: params.postId,
    },
  });

  if (!post) throw new Error("Post not found")å;
  return post;
};

export default function PostId() {
  const post = useLoaderData();

  return (
    <div>
      <h1>{post.title}</h1>
      {post.body}
    </div>
  );
}

 

다이내믹 라우팅도 잘 작동하고 있네요.

 

오늘은 리믹스 프레임워크의 서버사이드 함수인 action 함수 사용법에 대해 자세히 살펴보았습니다.

 

그럼.

그리드형