Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Postコンポーネントのリファクタリング #33

Open
hellotksan opened this issue Nov 5, 2024 · 0 comments
Open

Postコンポーネントのリファクタリング #33

hellotksan opened this issue Nov 5, 2024 · 0 comments

Comments

@hellotksan
Copy link
Owner

分割したコンポーネントをそれぞれファイルに分けることで、管理や保守がしやすくなります。以下は、ファイル構成の例です。

ディレクトリ構成の例

まず、PostComponent 用に専用のディレクトリを作成し、分割したコンポーネントをその中に保存します。以下のような構成にするのが一般的です。

src/
├── components/
│   ├── PostComponent/
│   │   ├── PostComponent.tsx      # メインのコンポーネント
│   │   ├── PostHeader.tsx         # ヘッダー部分のコンポーネント
│   │   ├── PostBody.tsx           # 本文部分のコンポーネント
│   │   ├── PostFooter.tsx         # フッター部分のコンポーネント
│   │   └── index.ts               # 各コンポーネントのエクスポート
│   └── ...

各ファイルの保存内容

  1. PostComponent.tsx

    • メインの PostComponent を定義するファイルです。
    • PostHeader, PostBody, PostFooter をインポートして組み合わせます。
  2. PostHeader.tsx

    • ユーザープロフィールや投稿日時を表示するための PostHeader コンポーネントを定義します。
  3. PostBody.tsx

    • 投稿内容(テキストや画像)を表示するための PostBody コンポーネントを定義します。
  4. PostFooter.tsx

    • いいねボタンやコメント数などの情報を表示する PostFooter コンポーネントを定義します。
  5. index.ts

    • 各コンポーネントをまとめてエクスポートし、PostComponent ディレクトリ全体を簡単にインポートできるようにします。
// src/components/PostComponent/index.ts
export { default as PostComponent } from "./PostComponent";
export { default as PostHeader } from "./PostHeader";
export { default as PostBody } from "./PostBody";
export { default as PostFooter } from "./PostFooter";

利用例

index.ts のおかげで、他のファイルで PostComponent を使用する際、ディレクトリごと簡単にインポートできます。

import { PostComponent } from "@/components/PostComponent";

このようにディレクトリごとに整理すると、各コンポーネントが独立してわかりやすくなり、保守性が向上します。

PostComponent はいくつかの異なる機能を持つため、それぞれの機能ごとにコンポーネントとして分離することで、コードの再利用性と可読性を高められます。以下のように分離するのはいかがでしょうか?

1. PostHeader コンポーネント

投稿したユーザーのプロフィールや投稿日を表示する部分を別コンポーネントにします。

import React from "react";
import { MoreVert } from "@mui/icons-material";
import { format } from "timeago.js";
import Image from "next/image";
import Link from "next/link";
import { User } from "@/types/user";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

interface PostHeaderProps {
  user: User | null;
  postId: string;
  createdAt: Date;
}

const PostHeader: React.FC<PostHeaderProps> = ({ user, postId, createdAt }) => (
  <div className="flex justify-between items-center mb-4">
    <div className="flex items-center">
      <Link
        href={{
          pathname: "/profile",
          query: user?.username ? { username: user.username } : {},
        }}
        className="no-underline flex items-center"
      >
        {user?.profilePicture ? (
          <Image
            src={`/assets/person/${user.profilePicture}`}
            alt="user-profile-picture"
            className="w-8 h-8 rounded-full object-cover"
            width={32}
            height={32}
          />
        ) : (
          <PersonIcon className="w-8 h-8 rounded-full" />
        )}
        <span className="ml-2 text-sm font-semibold">{user?.username}</span>
      </Link>
      <span className="text-xs ml-2">{format(createdAt)}</span>
    </div>
    <DropdownMenu>
      <DropdownMenuTrigger>
        <MoreVert />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <Link href={{ pathname: "/post-edit", query: { "post-id": postId } }}>
          <DropdownMenuItem>Edit</DropdownMenuItem>
        </Link>
        <DropdownMenuItem>Block</DropdownMenuItem>
        <DropdownMenuItem>Report</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  </div>
);

export default PostHeader;

2. PostBody コンポーネント

投稿内容(テキストや画像など)を表示するコンポーネントです。

import React from "react";

interface PostBodyProps {
  desc: string;
}

const PostBody: React.FC<PostBodyProps> = ({ desc }) => (
  <div className="my-5">
    <span className="block text-base">{desc}</span>
    {/* TODO: 投稿記事の画像 */}
  </div>
);

export default PostBody;

3. PostFooter コンポーネント

いいねボタンやコメント数を表示する部分を担当します。

import React from "react";
import FavoriteIcon from "@mui/icons-material/Favorite";

interface PostFooterProps {
  like: number;
  isLiked: boolean;
  handleLike: () => void;
}

const PostFooter: React.FC<PostFooterProps> = ({ like, isLiked, handleLike }) => (
  <div className="flex items-center justify-between">
    <div className="flex items-center">
      <FavoriteIcon
        className={`cursor-pointer w-6 h-6 mr-1 ${isLiked ? "text-red-500" : "text-gray-500"}`}
        onClick={handleLike}
      />
      <span className="text-base">{like}</span>
    </div>
  </div>
);

export default PostFooter;

4. PostComponent を分離したコンポーネントで再構成

上記のコンポーネントを使って PostComponent を再構築します。

import React, { useEffect, useState } from "react";
import axios from "axios";
import { USERS_ENDPOINT, POSTS_ENDPOINT } from "@/constants/api";
import LoadingSpinner from "@/components/elements/loadingSpinner/LoadingSpinner";
import { useAppSelector } from "@/hooks/useSelector";
import { Post as PostType } from "@/types/post";
import { User } from "@/types/user";
import PostHeader from "./PostHeader";
import PostBody from "./PostBody";
import PostFooter from "./PostFooter";

interface PostProps {
  post: PostType;
}

const PostComponent: React.FC<PostProps> = ({ post }) => {
  const { user: currentUser } = useAppSelector((state) => state.auth);
  const [like, setLike] = useState<number>(post.likes ? post.likes.length : 0);
  const [isLiked, setIsLiked] = useState<boolean>(false);
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(true);

  useEffect(() => {
    const fetchUser = async () => {
      setIsLoading(true);
      try {
        const response = await axios.get<User>(USERS_ENDPOINT, {
          params: { userId: post.userId },
        });
        setUser(response.data);
      } catch {
        alert("エラーが発生しました。");
      } finally {
        setIsLoading(false);
      }
    };
    fetchUser();
  }, [post.userId]);

  const handleLike = async () => {
    try {
      await axios.put(`${POSTS_ENDPOINT}/${post._id}/like`, {
        userId: currentUser?._id,
      });
      setLike(isLiked ? like - 1 : like + 1);
      setIsLiked(!isLiked);
    } catch {
      alert("エラーが発生しました。");
    }
  };

  return (
    <>
      {isLoading ? (
        <LoadingSpinner />
      ) : (
        <div className="w-full shadow-md rounded-lg mt-2 p-2">
          <PostHeader user={user} postId={post._id} createdAt={post.createdAt} />
          <PostBody desc={post.desc} />
          <PostFooter like={like} isLiked={isLiked} handleLike={handleLike} />
        </div>
      )}
    </>
  );
};

export default PostComponent;

説明

  • PostHeaderPostBodyPostFooter の3つのコンポーネントに分離し、それぞれの役割ごとにコードを整理しています。
  • PostComponent は、各コンポーネントを呼び出して全体のレイアウトを維持します。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant