티스토리 뷰
💥 리뷰 목록 구현하기
1. BookReview.tsx
import { BookReviewItem as IBookReveiwItem } from "@/models/book.model"
import styled from "styled-components"
import BookReviewItem from "./BookReviewItem"
interface Props {
reviews: IBookReveiwItem[]
}
function BookReview({ reviews }: Props) {
return (
<BookReviewStyle>
{reviews.map((review) => (
<BookReviewItem review={review} />
))}
</BookReviewStyle>
)
}
const BookReviewStyle = styled.div`
{ 생략 }
`
export default BookReview
2. BookReviewItem.tsx
import { 생략 }
interface Props {
review: IBookReveiwItem
}
const Star = (props: Pick<IBookReveiwItem, "score">) => {
return (
<span className="star">
{Array.from({ length: props.score }, (_, index) => (
<FaStar />
))}
</span>
)
}
function BookReviewItem({ review }: Props) {
return (
<BookReviewItemStyle>
<header className="header">
<div>
<span>{review.userName}</span>
<Star score={review.score} />
</div>
<div>
{formatDate(review.created_at)}
</div>
</header>
<div className="content">
<p>{review.content}</p>
</div>
</BookReviewItemStyle>
)
}
const BookReviewItemStyle = styled.div`
{ 생략 }
`
export default BookReviewItem
우선, BookReview.tsx 에서 리뷰들을 순회하면서 렌더링한다. 순회한 데이터들은 BookReviewItem.tsx 에서 뿌려지게 된다.
그리고 score 에 있는 숫자로 별 아이콘을 렌더링한다.
그럼 숫자만큼 별이 생성되고, 우리가 흔히 아는 리뷰에 있는 별점? 같은 느낌이 나게된다.
created_at 은 이전에 만든 formatDate 를 사용하여 년, 월, 일이 나오게 하였다.
💥 리뷰 목록 구현 - 결과물
그럼 이렇게 faker 로 만든 가짜 데이터로 리뷰 목록을 만들 수 있게 된다.
이름도 fakerKO 를 이용해 한국이름으로 나오게 만들었다. 글은 faker 에 있는 lorem 을 사용하였다.
💥 리뷰 작성 구현하기
1. BookReviewAdd.tsx
import { 생략 }
interface Props {
onAdd: (data: BookReviewItemWrite) => void
}
function BookReviewAdd({ onAdd }: Props) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<BookReviewItemWrite>()
return (
<BookReviewAddStyle>
<form onSubmit={handleSubmit(onAdd)}>
<fieldset>
<textarea {...register("content", { required: true })}></textarea>
{errors.content && <p className="error-text">리뷰 내용을 입력해 주세요.</p>}
</fieldset>
<div className="submit">
<fieldset>
<select {...register("score", { required: true, valueAsNumber: true })}>
<option value="1">1점</option>
<option value="2">2점</option>
<option value="3">3점</option>
<option value="4">4점</option>
<option value="5">5점</option>
</select>
</fieldset>
<Button size="medium" scheme="primary">작성하기</Button>
</div>
</form>
</BookReviewAddStyle>
)
}
const BookReviewAddStyle = styled.div`
{ 생략 }
`
export default BookReviewAdd
2. BookReview.tsx
import { 생략 }
interface Props {
reviews: IBookReveiwItem[]
onAdd: (data: BookReviewItemWrite) => void
}
function BookReview({ reviews, onAdd }: Props) {
return (
<BookReviewStyle>
<BookReviewAdd onAdd={onAdd} />
{reviews.map((review) => (
<BookReviewItem review={review} />
))}
</BookReviewStyle>
)
}
const BookReviewStyle = styled.div`
{ 생략 }
`
export default BookReview
3. review.ts
import { 생략 }
const mockReviewData: BookReviewItem[] = Array.from({ length: 8 }).map((_, index) => ({
id: index,
userName: faker.person.fullName(),
content: faker.lorem.paragraph(),
created_at: faker.date.past().toISOString(),
score: faker.number.int({ min:1, max: 5})
}))
export const reviewsById = http.get("http://localhost:9999/reviews/:bookId", () => {
return HttpResponse.json(mockReviewData, {
status: 200
})
})
export const addReview = http.post("http://localhost:9999/reviews/:bookId", () => {
return HttpResponse.json(
{
message: "리뷰가 등록되었습니다."
},
{
status: 200,
}
)
})
4. browser.ts
import { setupWorker } from "msw/browser"
import { addReview, reviewsById } from "./review"
const handlers = [reviewsById, addReview]
export const worker = setupWorker(...handlers)
5. review.api.ts
import { BookReviewItem, BookReviewItemWrite } from "@/models/book.model";
import { requestHandler } from "./http";
export const fetchBookReview = async (bookId: string) => {
return await requestHandler<BookReviewItem>("get", `/reviews/${bookId}`)
}
interface AddBookReviewResponse {
message: string
}
export const addBookReview = async (bookId: string, data: BookReviewItemWrite) => {
return await requestHandler<AddBookReviewResponse>("post", `reviews/${bookId}`, )
}
6. BookDetail.tsx
BookReviewAdd.tsx 즉, 리뷰작성 컴포넌트 페이지를 만들었다.
도서 상세 페이지에서 아래의 리뷰 목록 위에 나타나게 하기 위해, BookDetail 파일에 있는 BookReview 컴포넌트에서
onAdd 도 추가해서 addReview 를 호출할 수 있도록 해주었다.
BookRevieweAdd 파일에 보면, select 부분에 있는 valueAsNumber 가 새롭게 배운 부분 이었는데,
select 같은 경우엔 클릭한 option 의 value 를 전달할 때, string 으로 전달이 된다.
하지만 score 를 위한 option 인 만큼 숫자가 전달되어야 하기 때문에, valueAsNumber 를 따로 적어주었다.
review.api 에는 addBookReview 가 추가되었고, 인터페이스로 AddBookReviewResponse 가 새로 생겼다.
이건 message 를 전달하기 위함이다.
review.ts 파일에서 addReview 부분을 보면, return 을 json 으로 response 하기로 했다.
그래서 message 와 status 가 전달되게 만들었는데, 이 부분을 위해 만들어진 인터페이스이다.
useBook.ts 에서 response 받은 message 를 알림창으로 띄워주기 위해 showAlert 을 했다.
이 부분에서 res 가 undefined 일 수도 있기 때문에, 인터페이스를 따로 지정해 준 것이다.
💥 리뷰 작성 구현 - 결과물
이렇게 내용을 적고, 아래에 있는 별점을 클릭한 후, 작성하기를 누르면 "리뷰가 등록되었습니다." 라는 알림창이 뜨게 된다.
select 는 default 값으로 1번째 의 값이 나타나있기 때문에, 값이 없을 이유가 없어서 error text 는 적지 않았다.
대신, 내용은 꼭 적어야 하는 부분이므로 errors 를 주고, error text 를 만들어 주었다.
그럼 내용이 없을 땐, "리뷰 내용을 입력해 주세요." 라는 빨간색 글씨가 뜨게 된다.
'웹 개발 공부하기' 카테고리의 다른 글
[02.12] 메인 화면 구현하기 + 모바일 대응하기 (0) | 2025.02.18 |
---|---|
[02.11] 다양한 UI 경험 (3) | 2025.02.17 |
[02.11] 모킹 서버 (1) | 2025.02.17 |
[02.10] Book-Shop(Front) 중간 회고 2 (0) | 2025.02.16 |
[02.10] Book-Shop(Front) - 중간 회고 (0) | 2025.02.16 |