디테일 패스 변경 합니다.
detail/page.tsx 패스를
posts/[id]/page.tsx 패스로 변경 합니다.
이 구조는 넥스트제이에스가 제공하는 템플릿 입니다.
의심하지 말고 그냥 따라하면 지구에 평화가 옵니다.
import dataTexts from '../../data/dataText';
import { useState } from 'react';
import { usePosts } from '../../context/PostContext';
import { useParams } from 'next/navigation';
posts/[id]/page.tsx
패스가 변경되었으니 패스 수정 합니다.
데이터를 사용하기 위한 툴 임포트 합니다.
import { useState } from 'react'; // React의 상태 관리 훅을 가져옵니다.
import { usePosts } from '../../context/PostContext'; // 게시물 데이터를 제공하는 커스텀 훅을 가져옵니다.
import { useParams } from 'next/navigation'; // URL 파라미터를 가져오기 위한 Next.js 훅을 사용합니다.
디테일 데이터 인터페이스 빌딩 합니다.
interface Post {
id: number;
title: string;
summary: string;
date: string;
likeIt: string;
content: string;
}
임포트 아래에 포스트 인터페이스 빌딩 합니다.
// 게시물의 데이터 구조를 정의하는 인터페이스입니다.
interface Post {
id: number; // 게시물 고유 식별자
title: string; // 게시물 제목
summary: string; // 게시물 요약
date: string; // 작성일
likeIt: string; // 좋아요 수
content: string; // 게시물 내용
}
interface DetailContentProps {
post: Post;
count: number;
setCount: React.Dispatch<React.SetStateAction<number>>;
}
인터페이스포스트 아래에 디테일콘텐트프롭스 인터페이스 빌딩 합니다.
// Details 컴포넌트의 속성 타입을 정의하는 인터페이스입니다.
interface DetailContentProps {
post: Post; // 표시할 게시물 데이터
count: number; // 좋아요 카운트 상태
setCount: React.Dispatch<React.SetStateAction<number>>; // 좋아요 카운트 상태 변경 함수
}
필요 변수 세팅, 예외처리, 펑션디테일스에 데이터 전달 합니다.
const { posts, loading, error } = usePosts();
const params = useParams();
const postId = Number(params.id);
const [count, setCount] = useState(0);
const post = posts.find((p) => p.id === postId);
펑션 디테일 바로 아래에 필요 변수를 세팅 합니다.
const { posts, loading, error } = usePosts(); // Context에서 게시물 데이터를 가져옵니다.
const params = useParams(); // URL에서 파라미터(id)를 가져옵니다.
const postId = Number(params.id); // 문자열 ID를 숫자로 변환합니다.
const [count, setCount] = useState(0); // 추가 좋아요 수를 관리하는 상태를 생성합니다.
// 현재 URL의 ID와 일치하는 게시물을 찾습니다.
const post = posts.find((p) => p.id === postId);
if (loading) {
return <div className="m-3 text-center">데이터 로딩 중입니다...</div>;
}
if (error) {
return (
<div className="m-3 text-danger text-center">
데이터를 불러오는 데 실패했습니다.
</div>
);
}
if (!post) {
return (
<div className="m-3 text-center">
해당 포스트를 찾을 수 없습니다. (ID: {postId})
</div>
);
}
변수 아래에 로딩/에러 처리 합니다.
// 로딩 중일 때 표시할 내용
if (loading) {
return <div className="m-3 text-center">데이터 로딩 중입니다...</div>;
}
// 오류 발생 시 표시할 내용
if (error) {
return (
<div className="m-3 text-danger text-center">
데이터를 불러오는 데 실패했습니다.
</div>
);
}
// 해당 ID의 게시물이 없을 때 표시할 내용
if (!post) {
return (
<div className="m-3 text-center">
해당 포스트를 찾을 수 없습니다. (ID: {postId})
</div>
);
}
// 모든 조건이 충족되면 상세 내용과 댓글 폼을 표시합니다.
return (
<div>
<h4>{dataTexts[0].subCaps3}</h4>
<Details post={post} count={count} setCount={setCount} />
<Comment />
</div>
);
펑션디테일 리턴에 데이터 세팅 합니다.
// 모든 조건이 충족되면 상세 내용과 댓글 폼을 표시합니다.
return (
<div>
<h4>{dataTexts[0].subCaps3}</h4>
<Details post={post} count={count} setCount={setCount} /> {/* 게시물 상세 정보 컴포넌트 */}
<Comment /> {/* 댓글 입력 폼 컴포넌트 */}
</div>
);
디테일스 컴포넌트 데이터바인딩 하고 좋아요 기능 추가 합니다.
function Details({ post, count, setCount }: DetailContentProps)
위 설계에 따라 세가지 정보를 파라미터로 사용 합니다.
function Details({ post, count, setCount }: DetailContentProps){
return(
<>
<div className="card m-3">
<img
src={
`https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/girls/` +
(post.id + 1) +
`.PNG`
}
className="card-img-top"
alt="..."
/>
<div className="card-body">
<h5 className="card-title">{post.title}</h5>
<p className="card-text">
{post.content}
</p>
<p className="card-text">작성일: {post.date}</p>
<div className="mt-2">
<button
type="button"
className="btn active"
onClick={() => {
setCount(count + 1);
}}
data-bs-toggle="button"
aria-pressed="true"
>
❤️️
</button>
<span> {count + Number(post.likeIt)} </span>
</div>
<p className="card-text">
<small className="text-body-secondary">
Last updated 3 mins ago<br />
Default like: {post.likeIt}
</small>
</p>
</div>
</div>
</>
)
}
데이터바인딩 하고 좋아요 기능 추가 합니다.
// 게시물 상세 정보를 표시하는 컴포넌트입니다.
function Details({ post, count, setCount }: DetailContentProps){
return(
<>
<div className="card m-3">
<img
src={
`https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/girls/` +
(post.id + 1) +
`.PNG`
} // 게시물 ID에 해당하는 이미지를 가져옵니다.
className="card-img-top"
alt="..."
/>
<div className="card-body">
<h5 className="card-title">{post.title}</h5> {/* 게시물 제목 */}
<p className="card-text">
{post.content} {/* 게시물 내용 */}
</p>
<p className="card-text">작성일: {post.date}</p> {/* 작성일 표시 */}
<div className="mt-2">
{/* 좋아요 버튼 - 클릭 시 카운트를 증가시킵니다 */}
<button
type="button"
className="btn active"
onClick={() => {
setCount(count + 1); // 좋아요 카운트를 1 증가시킵니다.
}}
data-bs-toggle="button"
aria-pressed="true"
>
❤️️
</button>
<span> {count + Number(post.likeIt)} </span> {/* 기존 좋아요 + 추가된 좋아요 표시 */}
</div>
<p className="card-text">
<small className="text-body-secondary">
Last updated 3 mins ago<br /> {/* 마지막 업데이트 시간 (정적 텍스트) */}
Default like: {post.likeIt} {/* 원래 좋아요 수 표시 */}
</small>
</p>
</div>
</div>
</>
)
}
test

메인에서 포스트디테일 버튼 탭합니다.
해당 디테일뷰 유알엘로 잘 연결되는지 체크 합니다.
좋아요 탭 체크 합니다.
Completion
'use client';
import 'bootstrap/dist/css/bootstrap.min.css';
import dataTexts from '../../data/dataText';
import { useState } from 'react';
import { usePosts } from '../../context/PostContext';
import { useParams } from 'next/navigation';
interface Post {
id: number;
title: string;
summary: string;
date: string;
likeIt: number;
content: string;
}
interface DetailContentProps {
post: Post;
count: number;
setCount: React.Dispatch<React.SetStateAction<number>>;
}
interface DataText {
main: string;
subCaps1: string;
sub1: string;
subCaps2: string;
sub2: string;
subCaps3: string;
sub3: string;
footer: string;
}
export default function Detail() {
const { posts, loading, error } = usePosts();
const params = useParams();
const postId = Number(params.id);
const [count, setCount] = useState(0);
const post = posts.find((p) => p.id === postId);
if (loading) {
return <div className="m-3 text-center">데이터 로딩 중입니다...</div>;
}
if (error) {
return (
<div className="m-3 text-danger text-center">
데이터를 불러오는 데 실패했습니다.
</div>
);
}
if (!post) {
return (
<div className="m-3 text-center">
해당 포스트를 찾을 수 없습니다. (ID: {postId})
</div>
);
}
return (
<div>
<h4>{dataTexts[0].subCaps3}</h4>
<Details post={post} count={count} setCount={setCount} />
<Comment />
</div>
);
}
function Details({ post, count, setCount }: DetailContentProps) {
return (
<>
<div className="card m-3">
<img
src={
`https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/girls/` +
(post.id + 1) +
`.PNG`
}
className="card-img-top"
alt="..."
/>
<div className="card-body">
<h5 className="card-title">{post.title}</h5>
<p className="card-text">{post.content}</p>
<p className="card-text">작성일: {post.date}</p>
<div className="mt-2">
<button
type="button"
className="btn active"
onClick={() => {
setCount(count + 1);
}}
data-bs-toggle="button"
aria-pressed="true"
>
❤️️
</button>
<span> {count + Number(post.likeIt)} </span>
</div>
<p className="card-text">
<small className="text-body-secondary">
Last updated 3 mins ago
<br />
Default like: {post.likeIt}
</small>
</p>
</div>
</div>
</>
);
}
function Comment() {
return (
<div className="m-3">
<form>
<div className="mb-3">
<label for="exampleInputEmail1" className="form-label">
Comment
</label>
<input
type="email"
className="form-control"
id="exampleInputEmail1"
aria-describedby="emailHelp"
/>
<div id="emailHelp" className="form-text">
We'll share your story
</div>
</div>
<div className="mb-3">
<label for="exampleInputPassword1" className="form-label">
Password
</label>
<input
type="password"
className="form-control"
id="exampleInputPassword1"
/>
</div>
<div className="mb-3 form-check">
<input
type="checkbox"
className="form-check-input"
id="exampleCheck1"
/>
<label className="form-check-label" for="exampleCheck1">
Check me out
</label>
</div>
<button type="submit" className="btn btn-outline-secondary">
Submit
</button>
</form>
</div>
);
}
Comment ver
'use client'; // Next.js에서 클라이언트 컴포넌트임을 명시합니다.
import 'bootstrap/dist/css/bootstrap.min.css'; // 부트스트랩 CSS 스타일을 가져옵니다.
import dataTexts from '../../data/dataText'; // 텍스트 데이터를 가져옵니다.
import { useState } from 'react'; // React의 상태 관리 훅을 가져옵니다.
import { usePosts } from '../../context/PostContext'; // 게시물 데이터를 제공하는 커스텀 훅을 가져옵니다.
import { useParams } from 'next/navigation'; // URL 파라미터를 가져오기 위한 Next.js 훅을 사용합니다.
// 게시물의 데이터 구조를 정의하는 인터페이스입니다.
interface Post {
id: number; // 게시물 고유 식별자
title: string; // 게시물 제목
summary: string; // 게시물 요약
date: string; // 작성일
likeIt: number; // 좋아요 수
content: string; // 게시물 내용
}
// Details 컴포넌트의 속성 타입을 정의하는 인터페이스입니다.
interface DetailContentProps {
post: Post; // 표시할 게시물 데이터
count: number; // 좋아요 카운트 상태
setCount: React.Dispatch<React.SetStateAction<number>>; // 좋아요 카운트 상태 변경 함수
}
// 텍스트 데이터의 구조를 정의하는 인터페이스입니다.
interface DataText {
main: string; // 메인 텍스트
subCaps1: string; // 첫 번째 소제목 (대문자)
sub1: string; // 첫 번째 소제목 내용
subCaps2: string; // 두 번째 소제목 (대문자)
sub2: string; // 두 번째 소제목 내용
subCaps3: string; // 세 번째 소제목 (대문자)
sub3: string; // 세 번째 소제목 내용
footer: string; // 푸터 텍스트
}
// 상세 페이지의 메인 컴포넌트입니다.
export default function Detail() {
const { posts, loading, error } = usePosts(); // Context에서 게시물 데이터를 가져옵니다.
const params = useParams(); // URL에서 파라미터(id)를 가져옵니다.
const postId = Number(params.id); // 문자열 ID를 숫자로 변환합니다.
const [count, setCount] = useState(0); // 추가 좋아요 수를 관리하는 상태를 생성합니다.
// 현재 URL의 ID와 일치하는 게시물을 찾습니다.
const post = posts.find((p) => p.id === postId);
// 로딩 중일 때 표시할 내용
if (loading) {
return <div className="m-3 text-center">데이터 로딩 중입니다...</div>;
}
// 오류 발생 시 표시할 내용
if (error) {
return (
<div className="m-3 text-danger text-center">
데이터를 불러오는 데 실패했습니다.
</div>
);
}
// 해당 ID의 게시물이 없을 때 표시할 내용
if (!post) {
return (
<div className="m-3 text-center">
해당 포스트를 찾을 수 없습니다. (ID: {postId})
</div>
);
}
// 모든 조건이 충족되면 상세 내용과 댓글 폼을 표시합니다.
return (
<div>
<h4>{dataTexts[0].subCaps3}</h4> {/* 세 번째 소제목을 페이지 제목으로 사용합니다. */}
<Details post={post} count={count} setCount={setCount} /> {/* 게시물 상세 정보 컴포넌트 */}
<Comment /> {/* 댓글 입력 폼 컴포넌트 */}
</div>
);
}
// 게시물 상세 정보를 표시하는 컴포넌트입니다.
function Details({ post, count, setCount }: DetailContentProps) {
return (
<>
<div className="card m-3">
<img
src={
`https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/girls/` +
(post.id + 1) +
`.PNG`
} // 게시물 ID에 해당하는 이미지를 가져옵니다.
className="card-img-top"
alt="..."
/>
<div className="card-body">
<h5 className="card-title">{post.title}</h5> {/* 게시물 제목 */}
<p className="card-text">{post.content}</p> {/* 게시물 내용 */}
<p className="card-text">작성일: {post.date}</p> {/* 작성일 표시 */}
<div className="mt-2">
{/* 좋아요 버튼 - 클릭 시 카운트를 증가시킵니다 */}
<button
type="button"
className="btn active"
onClick={() => {
setCount(count + 1); // 좋아요 카운트를 1 증가시킵니다.
}}
data-bs-toggle="button"
aria-pressed="true"
>
❤️️
</button>
<span> {count + Number(post.likeIt)} </span> {/* 기존 좋아요 + 추가된 좋아요 표시 */}
</div>
<p className="card-text">
<small className="text-body-secondary">
Last updated 3 mins ago<br /> {/* 마지막 업데이트 시간 (정적 텍스트) */}
Default like: {post.likeIt} {/* 원래 좋아요 수 표시 */}
</small>
</p>
</div>
</div>
</>
);
}
// 댓글 입력 폼 컴포넌트입니다.
function Comment() {
return (
<div className="m-3">
<form>
<div className="mb-3">
<label for="exampleInputEmail1" className="form-label">
Comment {/* 댓글 입력 필드 레이블 */}
</label>
<input
type="email"
className="form-control"
id="exampleInputEmail1"
aria-describedby="emailHelp"
/>
<div id="emailHelp" className="form-text
