Skip to content
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

[장바구니 미션 Step 2] 윤생(이윤성) 미션 제출합니다. #220

Merged
merged 64 commits into from
May 28, 2023

Conversation

2yunseong
Copy link

@2yunseong 2yunseong commented May 22, 2023

안녕하세요 서니!
주말은 잘 보내셨나요??
step2 미션 제출합니다!

이번 미션 소감

recoil을 통해 어떤 데이터를 recoil로 다루어야 하는지, 또 어떤 상태가 파생된 상태인지 고민하는시간을 가졌습니다.
그리고 장바구니가 등록되는 로직은 이 부분을 참고해보았습니다!
(https://recoiljs.org/docs/guides/asynchronous-data-queries/#use-a-request-id)
또한 msw를 적용해보았는데요, 잘 적용했는지 모르겠군요..!! 특히 예외처리부분을 많이 신경쓰지 못한것 같아 아쉽습니다. (많은 피드백 부탁드립니다.)

궁금한 점

아래 코멘트로 몇가지 남겨보았어요!! 감사합니다!

2yunseong added 30 commits May 18, 2023 10:43
Copy link
Author

@2yunseong 2yunseong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

궁금한 부분 질문 남겼습니다..!

Comment on lines 4 to 7
export const cartSelects = atom<Set<number>>({
key: 'cartSelects',
default: new Set<number>(),
});
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cartSelects에는 사용자에 의해 선택된 cartId가 담깁니다.

아래 (총 금액, 배송비, 총 주문금액) 상태는 모두 이 cartSelects에 파생된 상태라고 보았습니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'선택' 여부를 상태로 관리한 이유가 궁금합니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

장바구니 선택 여부는 클라이언트만의 상태라고 생각해, 서버 상태만 받아오는 cart와는 분리하였습니다!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클라이언트만의 상태인데, 전역으로 관리해주신 이유가 있을까요? 장바구니 페이지 내에서만 지역적으로 관리가 되어도 괜찮을 정보인 것 같아서, 전역으로 관리해주신 이유도 궁금해요~

Comment on lines 11 to 14
interface RequestAction {
action: string;
payload?: any;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recoil 에대한 typescript를 적용하다.. 시간이 부족해 any를 적어두었습니다.. 따끔한 피드백 부탁드립니다

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자수하셨군요.. 검거..ㅋㅋ
image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request 요청에 대해서 payload가 어떤 것이 들어올 지 모르는 경우가 있는데요, 이 경우에는 any 보다는 unknown 타입을 활용하시거나, 제네릭을 활용해주면 더 좋을 것 같아요~

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get post patch delete 별로 payload를 구체화 해보았습니다!
(제네릭을 썼는데 실패해서 .. 나름 전부 명세해주는 걸로 구현해보았습니다!)

const { count, setCount } = useCount(quantity);
const { name, imageUrl, price } = product;
const [check, setCheck] = useState(false);
const [cartSelectsState, setCartSelectsState] = useRecoilState(cartSelects);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cartSelects에 대한 리코일 상태에 대한 이름을 어떻게 지어야할지 고민이에요. State라는 말이 무언가 불용어처럼 보여서요..!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const [cartSelectsState, setCartSelectsState] = useRecoilState(cartSelects);
const [cartSelects, setCartSelects] = useRecoilState(cartSelectsState);

와 같은 방식은 어떨까요? 사실 사용하는 측에서는 cartSelects가 props로 받아왔는지, 리코일에서 관리하고 있는 상태인지를 꼭 알 필요는 없을 것 같아요. 윤생이 이를 별도로 네이밍하고자 하는 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드를 읽는 제3자로 하여금 혼란을 줄수도 있다고 판단해 질문드렸습니다!

제 문제인데, 쓰는 곳곳마다 네이밍이 겹치지 않게 즉흥적으로 사용하는 것 같아서 서니는 네이밍을 어떻게 하는지 궁금했었던 의도였습니다!

제안주신 방법 너무 좋은 것 같아요!!

Comment on lines 24 to 26
const newCartSelects = new Set(Array.from(cartSelectSet));
newCartSelects.delete(cartId);
setCartSelects(newCartSelects);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set 자료형의 불변성을 지키기 위해 항상 새로운 Set을 만들거나 배열을 만들어 새로운 Set을 할당해주는데요, 더 좋은 방법이 없을까요?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불변성을 지키기 위함이라면, 지금 윤생이 사용해주신 방법말고는 저도 떠오르는 것이 없네요 😭

2yunseong

This comment was marked as duplicate.

Copy link

@sunhpark42 sunhpark42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 윤생, 리뷰가 늦었습니다. 리뷰 했다고 생각했는데, 제가 놓치고 있었더라구요 ㅠㅠ 죄송합니다.
남겨주신 코멘트에 답변 및 추가 코멘트 남겼습니다. 확인 부탁드려요.
이번 미션도 고생많으셨습니다.

src/api/api.ts Outdated
Comment on lines 1 to 6
export const productsQuery = async () => {
const response = await fetch('/products');
if (!response.ok) throw new Error();
const data = await response.json();
return data;
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 url을 제외하고는, 반복되는 api 요청이 있는데, 이 부분을 따로 추출해서 별도의 함수로 만들어봐도 좋을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한번 해보았는데, 제가 잘 이해했을까요?
8ac5fe5

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의도한 바로는 fetch 자체를 한 번 래핑해서 사용해줘도 좋을 것 같다였는데, 지금과 같이 response 를 다루어주는 방식도 괜찮은 것 같아요~ 👍

Comment on lines 4 to 7
export const cartSelects = atom<Set<number>>({
key: 'cartSelects',
default: new Set<number>(),
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'선택' 여부를 상태로 관리한 이유가 궁금합니다.

Comment on lines +17 to +20
let price = 0;
if (cartItem) {
price = cartItem.quantity * cartItem.product.price;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let 을 이용해서 price 를 설정하고, 하단에서 변경해주시고 계시는데요, let 을 사용해주신 이유가 특별히 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cartItem이 없으므로 방어적 코드를 작성해야 된다 생각해 if문을 통한 Type Guard로 구현하였습니다! 삼항연산자를 써도 되었을 것 같네요! 서니의 생각은 어떠신가요!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let 으로 선언한 변수는 언제 변경될지 알수 없기때문에, 조금 더 단순하게 얼리리턴을 사용해줬어도 좋을 것 같아요~

Comment on lines 11 to 14
interface RequestAction {
action: string;
payload?: any;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request 요청에 대해서 payload가 어떤 것이 들어올 지 모르는 경우가 있는데요, 이 경우에는 any 보다는 unknown 타입을 활용하시거나, 제네릭을 활용해주면 더 좋을 것 같아요~

});

export const cartState = selectorFamily<CartType[], any>({
key: 'exampleCartQuery',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key가 exampleCartQuery 인 이유가 있나용?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고치지않은.. 실수입니다 ㅠ 주의하겠습니다 😭

Comment on lines 25 to 33
useEffect(() => {
if (checkAll) {
const newCartSelects = cart.map((cartItem: CartType) => cartItem.id);
setCartSelectsState(new Set(newCartSelects));
}
if (!checkAll && cartSelectsState.size === cartTotal) {
setCartSelectsState(new Set());
}
}, [checkAll]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오히려 이 부분이 onClick 로직으로 빠져도 괜찮을 것 같아요.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

products에 대한 mockup 데이터라면, 파일 명을 보다 products 배열이라는 것을 알 수 있게 지으면 더 좋을 것 같아요.

Comment on lines 8 to 11
// TODO: 에러처리 스펙에 맞게 변경
const responseData = product ?? {
errorMsg: 'no exist data',
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 정해졌을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러 발생시 body에 아무것도 안 보내는 걸로 결정났습니다!

따로 명세는 없었는데, 백엔드와 통신하면서도 400 Bad Request만 보낸다고 하더라구요!

그런데 이걸 구현할 당시에는 백엔드가 없어서 에러 메세지를 전달해주어야 되지않을까 해서 이렇게 구현하였습니다 !!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src 안에 public 폴더가 또 있는 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msw 적용하다 잘못들어갔습니다!!

앞으로 주의깊게 보는 습관을 더 자주 들어야겠습니다 😰

Comment on lines 21 to 26
return res(
ctx.status(400),
ctx.json({
errorMsg: 'no exist data',
})
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요런 반복되는 부분은 하나로 추출해줘도 좋을 것 같아요~

Copy link
Author

@2yunseong 2yunseong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 서니!! 리뷰가 늦으셨다고 말씀하셨는데, 제가 늦게 보낸 부분도 있고, 오히려 페어기간 동안 다음 미션 집중할 수 있어서 좋았습니다!! 오늘에서야 딱 끝나서 보니까 좋네요!! 열심히 리뷰 반영 해보았습니다!

실수한 부분이나 놓친부분이 많아서 부끄럽네요 ..!!ㅎㅎ

Comment on lines 4 to 7
export const cartSelects = atom<Set<number>>({
key: 'cartSelects',
default: new Set<number>(),
});
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

장바구니 선택 여부는 클라이언트만의 상태라고 생각해, 서버 상태만 받아오는 cart와는 분리하였습니다!

Comment on lines +17 to +20
let price = 0;
if (cartItem) {
price = cartItem.quantity * cartItem.product.price;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cartItem이 없으므로 방어적 코드를 작성해야 된다 생각해 if문을 통한 Type Guard로 구현하였습니다! 삼항연산자를 써도 되었을 것 같네요! 서니의 생각은 어떠신가요!

});

export const cartState = selectorFamily<CartType[], any>({
key: 'exampleCartQuery',
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고치지않은.. 실수입니다 ㅠ 주의하겠습니다 😭

Comment on lines 27 to 42
switch (action) {
case 'GET':
return await cartQuery();
case 'POST':
const cartId = await postCartItemQuery(payload.productId);
await patchCartItemQuantityQuery(cartId, payload.quantity);
return await cartQuery();
case 'PATCH':
await patchCartItemQuantityQuery(payload.cartId, payload.quantity);
return await cartQuery();
case 'DELETE':
await deleteItemQuery(payload.cartId);
return await cartQuery();
default:
return await cartQuery();
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GET자체가 기본동작인데, GET이 아무동작없이 마지막에 한번만 하는 것이 어색하다는 생각이 들어 다음과같이 구현하였습니다!

각 동작마다 어떤 요청이 진행되는지 명확하게 표현해주고 싶어서 다음과 같이 표현했습니다.

다시보니 DRY 원칙을 어긴것 같아요! 수정해보도록 하겠습니다.

Comment on lines 25 to 27
<Suspense fallback={<Header>든든 배송 상품 (0개)</Header>}>
<CartListHeader />
</Suspense>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

약간의 편법을 사용하였는데.. pending(대기 상태) => fulfill시 깜빡이는 현상이 있어 숫자만 변경하려고 다음과같이 했습니다..
하지만 좀 더 찾아보니 useTransition 이 있어 적용했습니다!
해당 데이터가 오기 전까지 이전 데이터를 사용하는 거라, 현재 컴포넌트에 적합하다고 생각했습니다.

Comment on lines 20 to 24
const [checkAll, setCheckAll] = useState(false);

useEffect(() => {
setCheckAll(cartSelectsState.size === cartTotal && cartTotal !== 0);
}, [cartTotal, cartSelectsState]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 위의 check 상태와 같이, 불필요한 상태를 생성했다고 생각합니다!
수정하도록 하겠습니다 !!👍

Comment on lines 8 to 11
// TODO: 에러처리 스펙에 맞게 변경
const responseData = product ?? {
errorMsg: 'no exist data',
};
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러 발생시 body에 아무것도 안 보내는 걸로 결정났습니다!

따로 명세는 없었는데, 백엔드와 통신하면서도 400 Bad Request만 보낸다고 하더라구요!

그런데 이걸 구현할 당시에는 백엔드가 없어서 에러 메세지를 전달해주어야 되지않을까 해서 이렇게 구현하였습니다 !!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msw 적용하다 잘못들어갔습니다!!

앞으로 주의깊게 보는 습관을 더 자주 들어야겠습니다 😰

cartRequestAction({ action: 'GET' })
);

let countDebounceId = useRef<NodeJS.Timeout>();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

countDebounceId 를 변경하는 부분이 없는데 let 으로 지정해주신 이유가 있을까요?

그러네요..!! ref 자체가 바뀌는게 아닌 current가 변경되는건데, 이전에 그냥 변수를 사용해 id를 관리하려다 ref로 바꾸면서 const로 변경하지 못해 놓쳤습니다.

Comment on lines 11 to 14
interface RequestAction {
action: string;
payload?: any;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get post patch delete 별로 payload를 구체화 해보았습니다!
(제네릭을 썼는데 실패해서 .. 나름 전부 명세해주는 걸로 구현해보았습니다!)

Copy link
Author

@2yunseong 2yunseong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 서니!! 리뷰가 늦으셨다고 말씀하셨는데, 제가 늦게 보낸 부분도 있고, 오히려 페어기간 동안 다음 미션 집중할 수 있어서 좋았습니다!! 오늘에서야 딱 끝나서 보니까 좋네요!! 열심히 리뷰 반영 해보았습니다!

실수한 부분이나 놓친부분이 많아서 부끄럽네요 ..!!ㅎㅎ

Copy link

@sunhpark42 sunhpark42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 윤생 남겨주신 코멘트와, 반영된 코드 확인했습니다~ 답글 남겨둔 건 확인만 해주세요. 이번미션도 고생많으셨습니다~

Comment on lines 32 to 41
const countDebounce = useDebounce(() => {
setRequestAction({
action: 'PATCH',
payload: { cartId: id, quantity: count.value },
});
});

useEffect(() => {
if (countDebounceId.current) {
clearTimeout(countDebounceId.current);
}
countDebounceId.current = setTimeout(() => {
setRequestAction({
action: 'PATCH',
payload: { cartId: id, quantity: count.value },
});
}, 400);
}, [count, setRequestAction]);
countDebounce();
}, [count]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useEffect 부분까지 useDebounce 훅에서 처리해주고, 아이템 컴포넌트에서는 이렇게만 사용해줄 수 있어도 좋을 것 같아요~

useDebounce(() => {
    setRequestAction({
      action: 'PATCH',
      payload: { cartId: id, quantity: count.value },
    });
  });

@sunhpark42 sunhpark42 merged commit a142665 into woowacourse:2yunseong May 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants