- utils -> http-commons.js 주소 변경하기
- store -> member -> useHook, app -> layout -> bookmark
- src -> member -> book -> isbn -> bookmark -> page
- 구현 사항 - 기능: 등록/ 삭제/ 모두 삭제
- 등록: 도서 상세 페이지에서
- 삭제, 모두 삭제: 북마크 리스트 페이지에서 </aside>

1. store > book-mark.tsx
todo1.
제공할 상태와 상태를 변경할 함수에 대한 타입 설정하기
todo2.
createContext() 함수로 제공할 context 생성하기
interface BookContextType {
//todo1. 사용할 데이터 타입 정의
books: Book[];//여러 배열 가져오니까 [] 사용
registBookMark:(book: Book) => void; // 리턴 타입 없음
removeBookMark:(isbn: string) => void;
clearBookMark: () => void;
//todo2. createContext() 함수로 제공할 context 생성하기
const BookMarkContext = createContext<BookContextType | undefined>(undefined);
}
todo3.
타입에 해당하는 구현부를 Provider로 작성하여 리턴하기
todo4.
커스텀 훅: useMemberContext
export const BookMarkProvider = ({ children }: { children: ReactNode }) => {
const [books, setBook] = useState<Book[]>([]); // 상태 선언
const registBookMark = (book: Book) => {
setBook([...books, book]); //spread 함수
};
const removeBookMark = (isbn: string) => {
setBook(books.filter((book) => book.isbn !== isbn)); // 리렌더링하기 위해서
};
- export 사용해야 다른 파일*(layout)에서 import 할 수 있음
export하는 두가지 방식
- rsc 단축키 사용 후 const 화살표 함수, export 문장 사용하기
- export default function () {} 사용하기
📌 filter VS prev두 방식 비교
방식 예시 특징 ]
| 직접 참조 | setBook(books.filter(...)) | 상태값을 직접 사용함 (렌더링 시점에 따라 부작용 가능성 있음) |
| 함수형 업데이트 (prev) | setBook(prev => prev.filter(...)) | 가장 최신 상태 기반으로 동작함. 안정성 ↑, 추천! ✅ |
→ 비동기 상태 업데이트가 많거나, 상태 의존도가 높은 앱이라면 prev함수형 업데이트 사용하기
export const BookMarkProvider = ({ children }: { children: ReactNode }) => {
const [books, setBook] = useState<Book[]>([]);
const registBookMark = useCallback((book: Book) => {
setBook((prev) => (prev.find((item) => item.isbn === book.isbn) ? prev : [...prev, book]));
}, []);
const removeBookMark = useCallback((isbn: string) => {
setBook((prev) => prev.filter((item) => item.isbn !== isbn));
}, []);
const clearBookMark = useCallback(() => {
setBook([]);
}, []);
const returnValue = useMemo(
() => ({ books, registBookMark, removeBookMark, clearBookMark }),
[books]
);
return <BookMarkContext.Provider value={returnValue}>{children}</BookMarkContext.Provider>;
};
1. useCallback
함수를 기억하고 싶을 때
const memoizedFunc = useCallback(() => {
함수동작();
}, [의존값]);
- 함수 자체를 기억해서, 매번 새로 만들지 않음.
- 주로 자식 컴포넌트에 콜백 함수 props로 넘길 때 사용.
2. useMemo
렌더링할 때마다 바로 계산하지 않고,
값이 진짜 바뀔 때만 계산하고, 기억해두는 것
const memoizedValue = useMemo(() => {
return 계산결과;
}, [의존성]);
- 계산 결과: 복잡한 연산 또는 함수 호출
- [의존성]: 이 값이 바뀔 때만 다시 계산됨
2. app > layout.tsx
export default function RootLayout({
// return (...) 형태로 레이아웃 구조를 반환
children,
}: Readonly<{
children: React.ReactNode;
// children은 React.ReactNode 타입이며 Readonly이므로 props는 읽기 전용임
// 따라서 컴포넌트 내부에서는 children을 수정 불가
}>) {
return (
<html lang="en">
<body>
<QueryProvider>
<MemberProvider>
<BookMarkProvider>
<Navbar /> {/* 전역 변수 사용하기 때문에 감싼 형태로 BookMarkProvider 사용 */}
{children}
</BookMarkProvider>
</MemberProvider>
</QueryProvider>
</body>
</html>
);
}
- ReactNode
문자열, 숫자, JSX element, 배열 등 대부분의 렌더링 가능한 요소를 포괄함
- Readonly
읽는 전용의 props
3. app > {isbn} > page.tsx
// 중간에 useBookMarkContext 사용
const { registBookMark } = useBookMarkContext();
const handleRegistBookMark = useCallback(() => {
if (book) {
registBookMark(book);
alert("선택할 책을 즐겨찾기에 담아놨습니다.");
}
}, [book]);
// 맨 하단에 button onClick 추가
<button onClick={handleRegistBookMark}>북마크</button>
<button onClick={handleRemove}>삭제</button>
4. component > book_mark > BookMarkItem.tsx
const BookMarkItem = ({ book }: BookProps) => {
const { removeBookMark } = useBookMarkContext();
const handleRemove = useCallback(() => {
removeBookMark(book.isbn);
}, [book]);
5. app > book_mark > page.tsx
const BookMarkList = () => {
const { clearBookMark, books } = useBookMarkContext();
const handlerClear = useCallback(() => {
clearBookMark();
}, []);
{books.length > 0 ? (
books.map((book) => <BookMarkItem key={book.isbn} book={book}></BookMarkItem>)
// react가 알아볼 수 있게 key 값 필수이며 프라이머리 키인 book도 넣어줌
) : (
<tr>
<td colSpan={6}>북마크 내용이 비었습니다.</td>
</tr>
)}
'FrontEnd > Next.js' 카테고리의 다른 글
| [ Next.js ] Router (1) | 2025.04.21 |
|---|---|
| [ Next.js ] Axios (1) | 2025.04.17 |