-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1단계 - 장바구니 미션] 엘라(김현주) 미션 제출합니다 #2
Conversation
Co-authored-by: zereight <zereight@users.noreply.github.com>
Co-authored-by: zereight <zereight@users.noreply.github.com>
Co-authored-by: zereight <zereight@users.noreply.github.com>
Co-authored-by: zereight <zereight@users.noreply.github.com>
Co-authored-by: zereight <zereight@users.noreply.github.com>
Co-authored-by: zereight <zereight@users.noreply.github.com>
Co-authored-by: HyuuunjuKim <HyuuunjuKim@users.noreply.github.com>
Co-authored-by: HyuuunjuKim <HyuuunjuKim@users.noreply.github.com>
Co-authored-by: HyuuunjuKim <HyuuunjuKim@users.noreply.github.com>
Co-authored-by: HyuuunjuKim <HyuuunjuKim@users.noreply.github.com>
- firebase 연동 Co-authored-by: HyuuunjuKim <HyuuunjuKim@users.noreply.github.com>
Co-authored-by: HyuuunjuKim <HyuuunjuKim@users.noreply.github.com>
Co-authored-by: HyuuunjuKim <HyuuunjuKim@users.noreply.github.com>
9e94f04
to
72854dd
Compare
데모 페이지 둘러본 결과 주문 플로우가 잘못 된 것 같습니다. 라는 댓글을 먼저 남기고 코드를 볼게요. |
궁금한 점에 대한 답변 세부 코멘트에서 답을 찾으실 수도 있을것 같고 추가로 보충하자면.. 상태 갱신후 비동기 통신은, thunk 함수에서 하면 됩니다. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateShoppingCartItemsAsync 를 중심으로 수정이 조금 필요할 것 같습니다.
전반적으로 컴포넌트 분리는 잘 되어 있는것 같습니다.
src/redux/action.js
Outdated
productId: id, | ||
}); | ||
|
||
const updateShoppingCartItemsAsync = (schema, targetId, content) => async dispatch => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateShoppingCartItemsAsync에 문제가 될 포인트가 몇 가지 보입니다.
이를 개선해봅시다.
- 첫 번 째 파라미터인 schema에 "shoppingCart" 가 아닌 다른 값이 들어와도 됩니까? (또는 추후에라도 받아 줄 수 있도록 확장 가능합니까?)
- 이 schema가 컴포넌트(로직을 갖는 페이지 컴포넌트라고 하더라도)의 관심사(알고 있어야 하는 정보)입니까?
저는 두 질문의 답이 no 라고 생각됩니다.
requestTable에는 스키마를 전달해야 하는 것이 분명하지만, 그 스키마를 알고 있는 것은 updateShoppingCartItemsAsync 면 충분 할 것 같습니다.
외부 통신을 위한 스키마는 컴포넌트가 알아서 누군가에게 전달해야 하는 정보가 아닙니다.
그래서 첫 번째 파라미터인 schema 는 제거 되어야 할 것 같습니다.
아래 getMyShoppingCartAsync도 같아 보입니다.
- tragetId와 content 또한 정말 외부(컴포넌트)에서 받아야 할 정보입니까?
=> async dispatch => {
=> async (dispatch, getState) => {
로 수정하시어 getState() 를 통해 이 함수 내부에서 결정 할 수 있지는 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자프님의 질문에 대해서도 스스로 생각해보니 1번 2번 모두 No인 것 같습니다! 페이지 컴포넌트에서 스키마에 대한 정보를 알기보다는 비동기 로직을 사용하는 액션함수에서 직접 알고있어도 될 부분인 것 같습니다. 수정했습니다 ㅎ ㅎ
그리고 3번 질문에 대해서는 부끄럽지만 => async (dispatch, getState) => {
이렇게 해서 액션 함수에서 상태를 조회할 수 있는지 몰랐습니다.....😥.. 액션 함수로 상태를 조회할 수 있게되니 액션함수가 많이 수정되었습니다. 이에 맞춰서 아래 피드백에서 질문 주신 부분에 대한 답변 달았습니다. 😄😄😄
커밋 2f9443c
} | ||
}; | ||
|
||
const onClickDeleteButton = targetId => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
내부에서 분기를 치는 방법 보다
모두 삭제하는 함수와 단건만 삭제하는 함수로 나누는게 더 좋습니다.
더 명시적(이름이 명확하니)이고 변화가 생겼을 때 수정하기도 더 용이합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const deleteAllShoppingCartItem = () => {
if (!window.confirm(CONFIRM_MESSAGE.DELETE)) return;
dispatch(deleteAllShoppingCartItemAsync(checkedIdList));
};
const deleteShoppingCartItem = targetId => {
if (!window.confirm(CONFIRM_MESSAGE.DELETE)) return;
dispatch(deleteShoppingCartItemAsync(targetId));
};
위와 같이 분리하였습니다. 감사합니다😊
const newContent = { | ||
productIdList: myShoppingCartProductIds.filter(productId => !checkedIdList.includes(productId)), | ||
}; | ||
dispatch(updateShoppingCartItemsAsync(SCHEMA.SHOPPING_CART, myShoppingCartId, newContent)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 코드를 보고 추측컨데...
상품 추가 할 때 ADD_ITEM, 지울 때 DELETE_ITEMS 을 쓰려고 했는데 막상 하다보니 UPDATE_MY_SHOPPING_CART_ITEMS 하나로 쓰는게 더 좋지 않을까(혹읔 귀찮아서) 해서 이걸로 퉁치신게 아닌가 하는 싸늘한 느낌이 듭니다.
그렇다면(그렇지 않다고 하더라도-_-;)...추가 할 때는 ADD 지울 때는 DELETE 로 액션 타입을 명확하게 나누는게 좋습니다.
액션이 명확(이라고 쓰고 코드가 장황....) 하도록 작성하는게 리덕스의 묘미(?) 입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번 미션에서 가장 고민이었던 부분은 기존 상품목록 id 리스트
에서 새로 update된 상품목록 id 리스트
로 변경하는 로직의 위치였습니다 ㅎ ㅎ
현재는 해당 로직이 page 컴포넌트에 있는데요!
해당 로직이 thunk 함수 밖에 있다보니, 장바구니에 상품을 추가하든 삭제하든 결국 thunk 함수는 최종 update된 id리스트를 firebase에 PUT 하기만하면 된다고 생각하였습니다. 그래서 두 case의 비동기 로직을 updateShoppingCartItemsAsync()
로 공유하도록 작성했습니다!
그런데 현재는 해당 로직을 액션 함수 내로 옮겼고, 그러다보니 자연스럽게 비동기 로직을 가진 액션함수가 분리될 수 있었습니다!!!!!!!!!!!!!!!!!
const addShoppingCartItemAsync = newProductId => async (dispatch, getState) => {
const { id, productIdList } = getState().myShoppingCartReducer.myShoppingCart;
try {
const newContent = { productIdList: [...new Set([...productIdList, newProductId])] };
await requestTable.PUT(SCHEMA.SHOPPING_CART, id, newContent);
dispatch({
type: ADD_SHOPPING_CART_ITEM,
newProductId,
});
} catch (error) {
console.error(error);
}
};
const deleteShoppingCartItemAsync = targetId => async (dispatch, getState) => {
const { id, productIdList } = getState().myShoppingCartReducer.myShoppingCart;
try {
const newContent = { productIdList: productIdList.filter(productId => productId !== targetId) };
await requestTable.PUT(SCHEMA.SHOPPING_CART, id, newContent);
dispatch({
type: DELETE_SHOPPING_CART_ITEM,
targetId,
});
} catch (error) {
console.error(error);
}
};
그리고 여전히 고민인 부분은,,,, 액션 타입을 ADD_ITEM, DELETE_ITEM으로 나누어야할까? 하는 부분입니다 😥😥😥
위 두 액션함수 모두 update된 장바구니 상품Id List를 newContent로 가지게되는데, 리듀서에게 newContent
로 넣어줄지 아니면 지금처럼 newProductId
, targetId
를 넘길지 고민이 됩니다.
newContent 넘긴다
-> 액션 타입을 UPDATE_ITEM 하나만 가지게 돼서 명확하지 않다.newProductId
,targetId
를 넘긴다 -> 액션 타입은 명확해진다. 하지만, update된 장바구니 상품Id List를 다시 계산해야한다.
커밋 2f9443c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상황에 따라 케바케일수도 있고해서 이건 다양하게 코드를 바꿔가면서 경험과 시간으로 결국 스스로 해결해야 할 것 으로 보이네요.
엘라님은 충분히 스스로 답을 찾아내실거라 믿기에 위 고민과 직접적인 연관이 없을 수는 있지만 여기서 알아두시면 좋은 부분을 알려드릴게요.
- 이런 상황(하고자 하는게 추가, 삭제이지만 POST, DELETE가 아닌 업데이트)에서는 PUT 메서드보다는 PATCH 메서드가 옳습니다.(firebase에서 PATCH 메서드로 동작하는 API 가 있는지는 모르겠지만)
- 객체에서 id만 따로 뽑아내서 상태를 관리하는것보다, id 를 가진 객체 그 자체를 상태로 관리하는게 대부분의 경우 더 용이합니다.
- 추가라는 동작을 할 때는 추가하는 대상 객체가 필요하지만, 삭제라는 동작을 할 때는 삭제할 대상을 특정 할 수 있는 고유값(id) 또는 형태만 있으면 됩니다. 따라서 코드의 디자인상
dispatch({ type: ADD_SHOPPING_CART_ITEM, newProductId, });
이건 이상하다는 의미입니다.
ADD_SHOPPING_CART_ITEM 타입과 함께 있어야 하는것은 id가 아니라 add될 아이템 객체여야 하는것이죠.
이것은 위에 설명드린 2번과 맥락을 같이 하는 내용이기도 합니다.
글로 쓰니 내용 전달이 어려울 수 있지만 곰곰이 한 번 생각 해보시고 2단계에 한 번 시도 해보셔도 좋을 것 같습니다.
|
||
const [checkedIdList, setCheckedIdList] = useState([]); | ||
const [isAllChecked, setAllChecked] = useState(true); | ||
const [shoppingCartItemList, setShoppingCartItemList] = useState([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shoppingCartItemList야 말로 리덕스로 관리하면 좋을텐데 왜 state로 하셨는지 의아합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금 1단계에서는 API가 제공되지 않아, mock data를 임시적으로 만들어 사용하고있습니다 😁😁😁
제가 생각하기에 장바구니에 담기는 상품목록은 담긴 상품에 대한 id를 list로만 가지고 있어도 충분하다고 생각하였습니다.
그러다보니, 장바구니에 상품을 추가하고 삭제할 때는 장바구니에 담긴 상품에 대한 id list
를 수정하고, 따라서 shoppingCart에 담긴 productIdList를 리덕스로 관리하였습니다!
그리고 shoppingCartItemList은 장바구니에 담긴 상품에 대한 id list
와 상품 목록의 productId를 join(?)하여 가져온 상품 정보 state입니다.
-> 아마 2단계에서 제공되는 API에서는 shoppingCartItemList를 이러한 과정없이 바로 받을 수 있을 것 같습니다 ㅎ ㅎ
}, [productList, myShoppingCartProductIds]); | ||
|
||
useEffect(() => { | ||
if (isAllChecked) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onClickAllCheckBox 에서 구현하셔도 되었을텐데 이렇게 useEffect로 하신 이유가 궁금합니다.
상태가 비동기적으로 갱신되어 어떤 타이밍 문제를 겪으셔서 얍삽이(?)로 이렇게 하신거라면 onClickAllCheckBox 내에서 해결을 보시는게 좋을 것 같습니다.
한 컴포넌트 내에 useEffect가 많으면 혼란(코드를 읽기도, 동작을 예측하기도 어려워서)스럽습니다.
참고로 UX적으로도 모두 선택의 현재 동작은 조금 이상합니다.
(모두 선택 체크 하고 아이템 각각을 체크 해제 했는데 여전히 모두 선택은 체크가 되어 있는 상태)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 로직에 대한 useEffect를 삭제했습니다. 자프님 말대로 충분히 onClickAllCheckBox()
에서 할 수 있엇네요! 그리고 체크여부에 따른 UX 문제 역시 해결하였습니다 ㅎ ㅎ
커밋 1ed9a86
안녕하세요 자프님 🙌 자프님이 말씀하신대로 상품목록 -> 장바구니 -> 주문 -> 결제 flow에 있어서 잘못된 confirm 메세지와 장바구니 아이템을 비우는 로직의 잘못된 위치를 확인했습니다. 더 세밀하게 생각했어야 했는데, 장바구니 페이지에서 넘어간다고 바로 아이템을 삭제해버렸네요😥😥😥 수정했습니다. 감사합니다 ❗❗❗ 커밋 6c13771 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엘라님 편지받고 놀러와봤어요 (ㅋㅋㅋ)
src/components/Button/Button.js
Outdated
import PropTypes from 'prop-types'; | ||
import { Container } from './Button.styles'; | ||
|
||
const Button = ({ children, onClick }) => <Container onClick={onClick}>{children}</Container>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 이 Button 컴포넌트를 submit이 아닌 button type으로 사용하려면 바로 사용하지 못하고 props를 또 추가해줘야할 것 같아요.
- disabled만 주려고 해도 마찬가지구요
HTML button element의 props를 모두 받을 수 있게 확장해보면 어떨까요?
const Button = ({ children, onClick }) => <Container onClick={onClick}>{children}</Container>; | |
const Button = props => <Container {...props} />; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와 제이비님!!!!!!!!!!!!!!!!!! 지금 리덕스 비동기 로직에 허덕허덕 대고 있었는데,,,ㅠ ㅠ 알림 메일 받고 깜짝 놀랐네요 호호호호호호호호호호호
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와 type이나 다른 props에 대한 확장성 고려를 못했네요 ㅎㅎㅎㅎ 피드백 감사합니다 >_<
* chore: js로 react 웹팩 + babel 설정 * chore: Webpack + babel 설정하기 (TS로 설정) * chore: Styled-Component 설치 및 Webpack 설정 * chore: 프로젝트 절대경로 설정 * chore: react-router-dom 설정 * chore: Webpack 이미지, 폰트 로드 설정하기 * chores: eslint + prettier 설정 * chore: husky, lint-staged 설정 완료 * chore: Github Action 설정하기 * chore: recoil 설정 완료 * chore: storybook 설정 완료 [#2] * feat: Header UX/UI 구현 [#4] * feat: Spinner 컴포넌트 구현 [#5] * refactor(app): step1 코드 전체 복사 및 웹팩 마이그레이션 * chore: github 액션 테스트 코드 실행 추가 * chore: msw 셋팅 [#6] * fix(constant): 바뀐 서버 URL 수정 * feat(mocks): msw 임의의 백엔드 로직 구현 * refactor(MultiSelector): Compound Component로 MultiSelector 직접 구현 * refactor(type): Product 타입 수정 및 적용 * feat(img): 휴지통 아이콘 로컬에 저장 * feat(mock): CartHandler 코드 수정 * refactor(global): 공통된 a 태그 스타일 수정 * refactor(constant): URL 전역 상태 수정 * feat(CartItem): CartItem 컴포넌트 구현 * refactor(Header): 헤더 컴포넌트 구현 * feat(hooks): useQuery 커스텀 훅 구현 * feat(CartButton): CartButton 공통 컴포넌트 구현 * feat(CheckBox): CheckBox 공통 컴포넌트 구현 * refactor(useCart): useCart에 mock server 요청 로직 추가 * refactor(ProductList): ProductList 컴포넌트 리팩토링 * refactor(ProductItem): 공통 컴포넌트 CartButton으로 인한 코드 리팩토링 * refactor(atoms): product fetch 코드 수정 * feat(PaymentBox): PaymentBox 컴포넌트 구현 * feat(CartDetailPage): CartDetailPage 컴포넌트 구현 * feat(ProductPage): 기존에 사용하던 Suspense, ErrorBoundary 제거 * feat(fetcher): fetcher 함수 구현 * chore(eslintrc): eslintrc 설정 추가 * chore: 개발 환경 셋팅 수정 * chore: 배포 환경 설정 변경 * chore: 배포 환경 설정 변경 2 * chore: 배포 환경 설정 변경 3 * chore: 배포 개발 환경 셋팅 * chore: 배포 개발 환경 셋팅 4 * chore: 모든 파일 삭제 * fix: pull request 안되는 문제 해결 * refactor: useCart 테스트 코드 리팩토링 * feat: 에러 상황에 따른 에러 코드 보여주기 기능 추가 및 테스트 코드 추가 * refactor(useQuery): catch 구문일 때 error 처리로 코드 변경 * refactor(cartItem) 가독성을 위한 삼항연산자 제거
안녕하세요 자프님 😊 이번에 @zereight 와 함께 장바구니 미션을 진행한 엘라입니다~!
데모 페이지 https://react-shopping-cart-6509f.firebaseapp.com
🙌 미션 필수 요구사항
GNB
상품목록
장바구니
주문/결제
🙌 미션 심화 요구사항
주문목록
UI/UX
😁 중점적으로 봐주셨으면 하는 점
action
,reducer
가 알맞은 역할을 하고 있는지🎇 궁금한 점
비동기 통신에 따른 state 갱신이 필요한데 이러한 로직을 어디에 위치 시켜야하는지에 대해 고민이 많았습니다.
이렇게 되다보니 비동기 통신의 로직이 이곳 저곳에 위치하는 것 같아서 고민하다가, 무조건 비동기 통신을 먼저 실행한 후 성공했을시 상태를 변경하는 로직으로 수정하였는데요! 그러다보니 비동기 통신 전 새로운 update content를 만들어 내는 로직이 밖으로 빠지게 되면서 중복되고 있습니다 ㅜ 비동기 통신이 섞인 redux의 상태 업데이트의 흐름이 이렇게 흘러가는게 맞을까요? ㅜ ㅜ
잘 부탁 드리겠습니다 🥰🥰🥰