/reading-record-project

๐Ÿ“š ๋…์„œ ๊ธฐ๋ก ์‚ฌ์ดํŠธ

Primary LanguageTypeScript

reading-record-project

TypeScript, React์™€ Redux-saga๋กœ ๊ตฌํ˜„ํ•œ ๋…์„œ ๊ธฐ๋ก ์‚ฌ์ดํŠธ

Fastcampus ByteDgree์—์„œ ์ง„ํ–‰ํ•œ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค.


โš™ Stack

TypeScript

React

Redux

  • redux-saga

๐Ÿ–ผ UI

image


๐Ÿ“š Features

  • ์ฑ… ์ถ”๊ฐ€ / ์ฑ… ์ˆ˜์ • / ์ฑ… ์‚ญ์ œ / ์ฑ… ๋ชฉ๋ก ๋ณด๊ธฐ
  • ๋กœ๊ทธ์ธ / ๋กœ๊ทธ์•„์›ƒ
  • any ํƒ€์ž…์„ ์ตœ๋Œ€ํ•œ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ์ •ํ™•ํ•œ ํƒ€์ž… ์ถ”๋ก 
  • form ์‚ฌ์šฉ ์‹œ uncontrolled component ๋ฐฉ์‹ ์‚ฌ์šฉ
  • Container components์™€ Presentational components ๋ถ„๋ฆฌํ•˜์—ฌ ์ž‘์„ฑ
  • Ducks ํŒจํ„ด ์‚ฌ์šฉ

๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•œ ๊ธฐ๋Šฅ

  • ๋กœ๊ทธ์ธ / ๋กœ๊ทธ์•„์›ƒ
  • ์ฑ… ์ถ”๊ฐ€
  • ์ฑ… ์ˆ˜์ •
  • ์ฑ… ์‚ญ์ œ
  • ์ฑ… ๋ชฉ๋ก ๋ณด๊ธฐ

๊ตฌ์กฐ

  • Page

    • Signin
    • Home
    • Add
    • Detail
    • Edit

โฉ Redux-saga๋กœ ์ƒํƒœ๊ด€๋ฆฌ

export const { addBook, editBook, deleteBook, getBooks } = createActions(
  {
    ADD_BOOK: (book: BookReqType) => ({
      book,
    }),
    EDIT_BOOK: (bookId: number, book: BookReqType) => ({
      bookId,
      book,
    }),
    DELETE_BOOK: (bookId: number) => ({ bookId }),
  },
  'GET_BOOKS',
  options,
);

export function* sagas() {
  yield takeEvery(`${options.prefix}/GET_BOOKS`, getBooksSaga);
  yield takeEvery(`${options.prefix}/ADD_BOOK`, addBookSaga);
  yield takeEvery(`${options.prefix}/EDIT_BOOK`, editBookSaga);
  yield takeEvery(`${options.prefix}/DELETE_BOOK`, deleteBookSaga);
}

// ์ฑ… ๋ชฉ๋ก
function* getBooksSaga() {
  try {
    yield put(pending());
    const token: string = yield select(getTokenFromState);
    const books: BookResType[] = yield call(BookService.getBooks, token);
    yield put(success(books));
  } catch (e) {
    yield put(fail(new Error(e?.response?.data?.error || 'UNKNOWN_ERROR')));
  }
}

interface addBookSagaAction extends AnyAction {
  payload: {
    book: BookReqType;
  };
}

// ์ฑ… ์ถ”๊ฐ€
function* addBookSaga(action: addBookSagaAction) {
  try {
    yield put(pending());
    const token: string = yield select(getTokenFromState);
    const book = action.payload.book;
    const bookData = yield call(BookService.addBook, token, book);
    const books: BookResType[] = yield select(getBooksFromState);
    yield put(success(books.concat(bookData)));
    yield put(push('/'));
  } catch (e) {
    yield put(fail(new Error(e?.response?.data?.error || 'UNKNOWN_ERROR')));
  }
}

interface editBookSagaAction extends AnyAction {
  payload: {
    bookId: number;
    book: BookReqType;
  };
}

// ์ฑ… ์ˆ˜์ •
function* editBookSaga(action: editBookSagaAction) {
  try {
    yield put(pending());
    const token: string = yield select(getTokenFromState);
    const bookId = action.payload.bookId;
    const book = action.payload.book;
    const bookData = yield call(BookService.editBook, token, bookId, book);
    const books: BookResType[] = yield select(getBooksFromState);
    yield put(
      success(
        books.map((book) =>
          book.bookId === bookData.bookId ? bookData : book,
        ),
      ),
    );
    yield put(push('/'));
  } catch (e) {
    yield put(fail(new Error(e?.response?.data?.error || 'UNKNOWN_ERROR')));
  }
}

interface delteBookSagaAction extends AnyAction {
  payload: {
    bookId: number;
  };
}

//์ฑ… ์‚ญ์ œ
function* deleteBookSaga(action: delteBookSagaAction) {
  try {
    yield put(pending());
    const token: string = yield select(getTokenFromState);
    const bookId = action.payload.bookId;
    yield call(BookService.deleteBook, token, bookId);
    const books: BookResType[] = yield select(getBooksFromState);
    yield put(
      success(books.filter((book) => book.bookId !== action.payload.bookId)),
    );
  } catch (e) {
    yield put(fail(new Error(e?.response?.data?.error || 'UNKNOWN_ERROR')));
  }
}