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

Feat/#59 토스트 컴포넌트 구현 #73

Merged
merged 21 commits into from
Jul 19, 2023
Merged

Feat/#59 토스트 컴포넌트 구현 #73

merged 21 commits into from
Jul 19, 2023

Conversation

Creative-Lee
Copy link
Collaborator

@Creative-Lee Creative-Lee commented Jul 18, 2023

📝작업 내용

  • 토스트 컴포넌트 구현
  • 토스트 Provider 및 Context 구현
  • Context 커스텀 hook 구현
  • 애니메이션 추가(마운트 시)

toast

💬리뷰 참고사항

  • Provider와 Context 구현으로 코드 전역에서 토스트를 사용할 수 있도록 했습니다.

    • 사용처에서 showToast 함수 하나만으로 선언적으로 사용할 수 있습니다.
  • 기본 글로벌 스타일을 정의하고 커밋했습니다.

    • 스토리북에 글로벌 스타일을 적용하기 위한 패키지도 하나 추가했어요.
  • svg와 같은 assets에 대한 설정이 없어서, 웹팩 설정이 필요해보입니다!

코드 전반적인 스타일이나, 의문을 모두 적어주시면 감사하겠습니다!

#️⃣연관된 이슈

close #59
close #82

@Creative-Lee Creative-Lee added [ 🌞 FE ] 프론트엔드 크루들의 빛나는 개발 이야기 하나둘셋 호! ✨ Feat 꼼꼼한 기능 구현 중요하죠 labels Jul 18, 2023
@Creative-Lee Creative-Lee self-assigned this Jul 18, 2023
Copy link
Collaborator

@cruelladevil cruelladevil left a comment

Choose a reason for hiding this comment

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

👍👍
토스트 이쁘게 구현 잘되었네요!!
window가 빠진 부분 외에 큰 문제는 없는 것 같아요~
두 가지 의견만 추가로 남길게요!

💬 useToastContext를 이용해서 사용하는 것이라면 네이밍을 useToast라고 해도 괜찮을 것 같아요.

// @/components/@common/Toast/index.ts
export { default as useToast } from './hooks/useToastContext';

// 사용처
import { useToast } from '@/components/@common/Toast';

💬 showToast와 hideToast가 덜 분리 된 것 같아요.
toast를 띄울 때, toast가 사라질 때의 행동을 조금 더 구분해주면 좋을 것 같아요.

const showToast = useCallback((message: string) => {
  if (toastTimer.current !== null) return;

  setIsToastShow(true);
  setMessage(message);

  const timer = window.setTimeout(hideToast, 2000);
  toastTimer.current = timer;
}, []);

const hideToast = useCallback(() => {
  if (toastTimer.current === null) return;

  setIsToastShow(false);
  setMessage('');

  window.clearTimeout(toastTimer.current);
  toastTimer.current = null;
}, []);

frontend/src/components/@common/Toast/ToastProvider.tsx Outdated Show resolved Hide resolved
frontend/src/components/@common/Toast/ToastProvider.tsx Outdated Show resolved Hide resolved
frontend/src/components/@common/Toast/ToastProvider.tsx Outdated Show resolved Hide resolved
frontend/src/components/@common/Toast/ToastProvider.tsx Outdated Show resolved Hide resolved
import { styled } from 'styled-components';
import type { PropsWithChildren } from 'react';

const ToastList = ({ children }: PropsWithChildren) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💬 토스트 리스트의 용도가 궁금해요! 😯

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Jul 18, 2023

Choose a reason for hiding this comment

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

리스트 컴포넌트는 현재 토스트 컴포넌트의 위치를 조정하는 역할을 합니다!
(리스트 컴포넌트가 absolute 속성을 가지게 했어요!)

또 이후에 토스트가 메세지가 여러개 올라와야한다는 요구사항도 충분히 생길 수 있다고 생각해서
추후 확장성을 생각해 만들게 되었어요.
만약 확장한다면, toast 객체를 가지고 이를 순차적으로 랜더링 하는 역할로 사용될거에요

정리하면,
(여러개의)토스트가 랜더링 될 위치를 지정하고
토스트의 랜더링을 담당할 컴포넌트 입니다.

frontend/src/components/@common/Toast/Toast.style.tsx Outdated Show resolved Hide resolved
Copy link
Collaborator

@ukkodeveloper ukkodeveloper left a comment

Choose a reason for hiding this comment

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

도밥 너무 고생 많았어요. 오랜만에 context API를 통해 시도한 점 멋집니다!
궁금한 것들이 있는데요.

  1. ToastProvider는 전역에서 한 번만 사용할 수 있는 컴포넌트일까요?
  2. Storybook에는 Toast 컴포넌트로 테스트하셨는데, Toast 컴포넌트만 독립적으로 사용해도 되는 것일까요?

개인적으로 네이밍만 봐서는 어떤 역할을 하는 것인지 확인하기 힘들었어요.

  1. useToastContext는 호출하면 showToast를 반환하잖아요. 그러면 useShowToast나 useTriggerToast가 더 직관적이지 않을까요?
  2. ToastList는 커멘트로 남겼습니다.
  3. useToastContext의 반환 값이 ToastContextProps타입인데, 타입만 봐서는 어떤 값인지 예측하기가 힘들었어요.

import { ToastContext } from '../ToastProvider';

const useToastContext = () => {
const value = useContext(ToastContext);
Copy link
Collaborator

Choose a reason for hiding this comment

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

value가 어떤 값인지 한눈에 확인하기 어려운 것 같아요. 해당 값이 무엇인지 알려면 ToastContext까지 들어가야 알 것 같습니다.

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

코멘트를 보고 고민을 해봤는데요!
커스텀 훅인데 return type이 바로 보이지 않는게 다소 어색하긴 하네요
한편으로는 이 부분은 useContext를 사용한다면 언제든 있을 수 있는 불편함 같아요..!

interface가 무엇인지 눌러보는 방법외에 바로 타입을 확인할 수 있는 방법이 있을까요?
따로 대화해보고 싶네요!


const useToastContext = () => {
const value = useContext(ToastContext);
if (!value) throw new Error('토스트 컨텍스트에 제공되는 값이 없습니다.');
Copy link
Collaborator

Choose a reason for hiding this comment

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

💬 '토스트 컨텍스트에 제공되는 값이 없습니다.' 라는 말이 context provider에 value를 넣지 않았을 때 인가요? -> 그러면 toastProvider을 사용자가 다른 곳에서도 사용할 수 있게끔 설계를 하신 걸까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  1. context provider에 value를 넣지 않았을 때 인가요?

네 맞습니다! 아래 그림의 type 처럼 showToast를 제공해야하는데, 개발자가 실수했을 경우를 대비한 ERROR에요!

null 유니온 타입을 사용하지 않고, showToast Type을 만족하는 기본함수로 초기화 해도 되지만,
이 방식보다는 null을 할당하고 에러를 띄워서 개발자의 실수를 잡아주고 싶었어요.

실수로 provider를 씌워주지 않거나, provider 영역 밖에서 사용하는 경우 에러가 발생하겠죠!

image
image

  1. toastProvider을 사용자가 다른 곳에서도 사용할 수 있게끔 설계를 하신 걸까요?

네 맞습니다!
코드에서는 전역적으로 감싸두었지만, 이후 언제든지 지역적으로 사용할 수 있습니다.
그런 의미에서는 제가 아예 전역에 감싸두지 않고 설명을 남겨놓는 것이 좋았겠네요!
이 부분 반영하도록 할게요!

@@ -0,0 +1,25 @@
import { keyframes } from 'styled-components';

export const fadeInBottomToUp = keyframes`
Copy link
Collaborator

Choose a reason for hiding this comment

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

💬 재사용을 위해서 파일로 분리하신 걸까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

키프레임 코드가 워낙 길다보니 컴포넌트 내에 두기엔 가독성이 좋지 않았습니다.
애니메이션의 동작을 변수로 설명하고 가독성도 챙기고 싶어서 파일로 분리했어요!

초기에는 키프레임 재사용이 가능할거라고 생각해서 src/styles 디렉터리에 분리했었는데,
토스트 컴포넌트 외에는 적용하기 힘들어 보여서 토스트 관련 디렉터리에 두었습니다!

Comment on lines +4 to +15
const ToastList = ({ children }: PropsWithChildren) => {
return <Position>{children}</Position>;
};

export default ToastList;

const Position = styled.div`
position: fixed;
bottom: 10%;
left: 50%;
transform: translate(-50%, 0);
`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
const ToastList = ({ children }: PropsWithChildren) => {
return <Position>{children}</Position>;
};
export default ToastList;
const Position = styled.div`
position: fixed;
bottom: 10%;
left: 50%;
transform: translate(-50%, 0);
`;
const ToastList = styled.div`
position: fixed;
bottom: 10%;
left: 50%;
transform: translate(-50%, 0);
`;
export default ToastList
  1. 💬 위와 아래에 차이가 있는 것일까요? 스타일만 있는 컴포넌트인 것 같은데 따로 react component로 선언한 이유가 궁금해요.
  2. ToastList라고 하면 여러 Toast가 띄워지는 ui로 생각이 드는데, 나중에 구현할 것을 생각하고 이름을 지으신 걸까요?

Copy link
Collaborator Author

@Creative-Lee Creative-Lee Jul 18, 2023

Choose a reason for hiding this comment

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

  1. 개인적으로는 위쪽을 선호합니다~ 추후 컨벤션에 따라 styled.tsx 파일로 분리될 로직이라 따로 이야기 나눠보죠!
    ts vs tsx 도 한번 이야기 나눠보고 싶네요!

  2. 맞습니다~! 코난 리뷰에 이에 대한 자세한 답변을 해서 링크 남길게요!
    Feat/#59 토스트 컴포넌트 구현 #73 (comment)

Comment on lines 4 to 15
import type { PropsWithChildren } from 'react';

interface ToastContextProps {
showToast: (message: string) => void;
}

export const ToastContext = createContext<null | ToastContextProps>(null);

const ToastProvider = ({ children }: PropsWithChildren) => {
const [isToastShow, setIsToastShow] = useState(false);
const [message, setMessage] = useState('');
const toastTimer = useRef<NodeJS.Timeout>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 와우 NodeJs.Timeout 타입인 것을 처음 알았네요. 그리고 timer을 useRef로 사용한 점 배워갑니다.
💬 createContext는 기본값으로 null을 주었는데 useRef는 인자를 비워둔 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

어헛 다행입니다
이 부분 코난이 정정해줬는데, 우코에게 바로 메타인지 들어갈게요

우선 해당 Ref의 타입은 잘못되었어요.
제가 작성한 코드가 실행될 런타임은 node.js 가 아닌 브라우저입니다.
setTimeout 대신 window.setTimeout 으로 명시함으로서 브라우저에서 실핼될 코드임을 알려줄 수 있습니다.
TS가 이 부분을 혼동하지 않게하는 역할도 있구요

때문에 해당 Ref type은 현재 아래와 같이 변경되었습니다!

  const toastTimer = useRef<number | null>(null);

간단하고 명료하게 설명된 블로그 링크 첨부합니다.

1. toastTimer ref type 변경
2. timeout관련 메서드 window 객체에서 사용
3. Timer ref type 변경으로 인한 show/hide 메서드 내 로직 변경(null 처리 등)
4. setter 순서 의미에 맞게 변경
컴포넌트 제공자의 입장으로 전역에 씌워놓은 Provider태그는 혼동을 줄 수 있어 삭제함.
@Creative-Lee
Copy link
Collaborator Author

Creative-Lee commented Jul 18, 2023

@cruelladevil @ukkodeveloper 🖐 우선 리뷰 감사합니다.ㅎㅎ
모든 리뷰 사항 확인하고 반영했습니다!

우코의 질문중 코멘트로 답변하지 못한 내용 여기에 남겨요!

Storybook에는 Toast 컴포넌트로 테스트하셨는데, Toast 컴포넌트만 독립적으로 사용해도 되는 것일까요?

이 부분은 의도하지 않았어요.
hook과 반환 함수를 통해 구현부에 대한 이해 없이 선언적으로 사용하도록 하고자 의도했습니다.
스토리가 단품이라 오해가 있을 수 있을 것 같지만, 그렇다고 스토리를 작성하지 않을순 없다고 생각했어요!

나머지 부분에 대한 씽크는 같이 맞춰보죠~ㅎㅎ
아래 반영사항 확인해 주시고 답변주세요!

📜 리뷰 반영 요약표

번호 리뷰 내용 코멘트 내용 반영 커밋
1 toastTimer ref type #73 (comment) -
2 window 객체 사용하기 #73 (comment) -
3 show/hide 메서드 내 로직 개선 코멘트 본문 -
4 setter 순서 변경 #73 (comment) 7913f8d
5 css 축약형 #73 (comment) 4e54435
6 스토리 기본인자 #73 (comment) 85cfb04
7 혼동을 주는 전역 Provider 태그 #73 (comment) 5ec25d6

이외 PR에 포함된 기능

  1. reset css 추가
  2. styled-components theme 설정 및 적용
  3. Layout 박스 설정
  4. 이미지 에셋 관련 웹팩 설정 추가

@Creative-Lee Creative-Lee merged commit 57c61ed into develop Jul 19, 2023
@Creative-Lee Creative-Lee deleted the feat/#59 branch July 19, 2023 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[ 🌞 FE ] 프론트엔드 크루들의 빛나는 개발 이야기 하나둘셋 호! ✨ Feat 꼼꼼한 기능 구현 중요하죠
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants