언어/React.js

[React] typescript zustand 이용하기

홍시_코딩기록 2024. 12. 19. 23:20

 

 

내가 요즘 잘 이용하고 있는 북적북적 이라는 어플을 웹으로 만들어보면서 학습하고 있다.

전에 고캠핑을 만들었을 땐 파이어베이스를 이용해서 했는데 이번엔 zustand를 접해 볼 기회가 있어 적응해볼 겸 적용해보았다.

zustand를 이용해 로컬 스토리지에 책 데이터(독서한 책)를 저장하고 있다.

 

 


책 정보 types

더보기
// 상품 조회 데이터
export interface BookData {
  title: string;
  author: string;
  cover: string;
  description: string;
  isbn: string;
  isbn13: string;
  link: string;
  publisher: string;
  pubDate?: string;
  subInfo?: {
    itemPage: number;
  };
}

// 서재 저장 데이터
export interface LibraryData {
  books: LibraryBook[];
  readBooks: LibraryBook[];
  readingBooks: LibraryBook[];
  addBook: (data: LibraryBook) => void;
  deleteBook: (bookId: string) => void;
}
export interface LibraryBook extends BookData {
  bookState: boolean;
  startDate?: Date | null;
  pagePercent?: boolean | null;
  endDate?: Date | null;
  starRating?: number | undefined;
  review?: string | null;
  pageNum?: number | null;
  itemPage?: number;
}

 

// 책 정보 조회 알라딘 api

import { BookData } from 'types/bookData';
import { create } from 'zustand';

interface BookStore {
  bookData: BookData[] | null;
  fetchBookData: (isbn: string) => Promise<void>;
}

// 책 정보 조회 api

const useBookStore = create<BookStore>((set) => ({
  bookData: null,
  fetchBookData: async (isbn) => {
    try {
      const response = await fetch(
        `
                  https://tranquil-tundra-65213-03a93afc8c4f.herokuapp.com/http://www.aladin.co.kr/ttb/api/ItemLookUp.aspx?ttbkey=${
                    process.env.REACT_APP_TTB_KEY
                  }&itemIdType=ISBN13&ItemId=${isbn}&output=js&Version=20131101
              `
      );

      const data = await response.json();
      set({ bookData: data.item });
    } catch (error) {
      console.log(error);
    }
  }
}));

export default useBookStore;

 

알라딘 블로그에 보면

요청 URL샘플 : http://www.aladin.co.kr/ttb/api/ItemLookUp.aspx?ttbkey=TTBKey&itemIdType=ISBN13&ItemId=도서의ISBN&output=xml&Version=20131101

 

이라고 나와있다! TTBKey엔 발급받은 자신의 key를 itemId는 도서의 isbn을 넣으면 되는데 도서 정보에서 isbn이 아니라 isbn13을 넣어야 상품 조회가 된다!

 


https://blog.aladin.co.kr/openapi/category/29154402?communitytype=MyPaper

 


// 책 저장하기

북적북적 어플에서는 읽은책, 읽고 있는 책, 읽고 싶은 책, 중단한 책 네가지로 나뉘는데 난 읽은책, 읽고 있는 책만 사용해서 두 가지만 넣었다. (연습용으로 만든거라 이 외에도 빠진 기능들이 있다.)

 

<기존 코드>

더보기
import { LibraryData } from 'types/bookData';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

// 데이터 유지: persist 추가 (로컬스토리지에 저장)

const useLibraryStore = create<LibraryData>()(
  persist(
    (set) => ({
      books: [],
      readBooks: [],
      readingBooks: [],
      addBook: (data) =>
        set((state) => {
          const updateBooks = [
            ...state.books,
            {
              bookState: data.bookState,
              startDate: data.startDate,
              endDate: data.endDate,
              starRating: data.starRating,
              review: data.review,
              pagePercent: data.pagePercent,
              pageNum: data.pageNum,
              // 책 정보
              title: data.title,
              cover: data.cover,
              author: data.author,
              itemPage: data.itemPage,
              link: data.link,
              description: data.description,
              publisher: data.publisher,
              isbn: data.isbn,
              isbn13: data.isbn13
            }
          ];

          // 책 update
          const readBooks = updateBooks.filter(
            (book) => book.bookState === true
          );
          const readingBooks = updateBooks.filter(
            (book) => book.bookState === false
          );

          return {
            books: updateBooks,
            readBooks: readBooks,
            readingBooks: readingBooks
          };
        }),
    }),
    { name: 'library' } // 로컬 스토리지에 'library'라는 키로 저장
  )
);

export { useLibraryStore };

원래는 books 저장한 책 전체 목록만 만들었다가 서재나 다른 페이지에서 읽은 책, 읽고 있는 책 상태별로 데이터가 필요했다. 매 화면마다 데이터를 필터링 하는 것 보다 필터링 된 데이터를 가져오는 것이 좋을 것 같다고 생각이 들었다.

그래서 책을 저장할 때 addBooks 에서 readBooks, readingBooks를 같이 업데이트 하는 방법으로 하려 했으나 이렇게 하는 건 books가 업데이트 될 때마다 readBooks, readingBooks도 매번 업데이트 되어야 하니까 불필요한 작업이 되는 것 같아서 방법을 바꿨다.

 

<수정 코드>

const useLibraryStore = create<LibraryData>()(
  persist(
    (set) => ({
      books: [],
      addBook: (data) =>
        set((state) => ({
          books: [
            ...state.books,
            {
              bookState: data.bookState,
              startDate: data.startDate,
              endDate: data.endDate,
              starRating: data.starRating,
              review: data.review,
              pagePercent: data.pagePercent,
              pageNum: data.pageNum,
              // 책 정보
              title: data.title,
              cover: data.cover,
              author: data.author,
              itemPage: data.itemPage,
              link: data.link,
              description: data.description,
              publisher: data.publisher,
              isbn: data.isbn,
              isbn13: data.isbn13
            }
          ]
        })),
      deleteBook: (bookId: string) =>
        set((state) => ({
          books: state.books.filter((book) => book.isbn13 !== bookId)
        }))
    }),
    { name: 'library' } // 로컬 스토리지에 'library'라는 키로 저장
  )
);

 

import { useLibraryStore } from 'store/useLibraryStore';

export const useReadBook = () => {
  const { books } = useLibraryStore();

  const readBooks = books.filter((book) => book.bookState === true);
  const readingBooks = books.filter((book) => book.bookState === false);

  return { readBooks, readingBooks };
};



// 사용 예시

export default function TabList() {
  const { readBooks } = useReadBook();

  return (
    <BookList>
      {readBooks
        .slice()
        .reverse()
        .map((book) => (
          <li key={book.isbn13}>
            <Link to='/home'>
              <img src={book.cover} alt={book.isbn13} />
              <p className='title text_ellipsis'>{book.title}</p>
              <p className='sub_title mt_4 text_ellipsis'>{book.author}</p>
              <StarRating readonly rating={book.starRating} />
            </Link>
          </li>
        ))}
    </BookList>
  );
}

 

원래대로 책 데이터는 books로만 관리하고 커스텀 훅을 만들어서 사용했다.

 

 

 

로컬스토리지에서 저장한 책 데이터를 확인할 수 있다.

 

 

 

 

 

'언어 > React.js' 카테고리의 다른 글

[React.js] canvas 를 이용한 게임  (0) 2025.02.03
[cors 오류] 알라딘 api cors 연결하기  (0) 2025.01.11
useCallback()  (0) 2024.10.22
Side Effects 다루기  (0) 2024.10.18
[React] 내가 보려고 쓴 리액트  (0) 2023.10.26