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

Style/#286: 꿀팁 페이지 디자인 수정 #287

Merged
merged 25 commits into from
Nov 28, 2023
Merged

Style/#286: 꿀팁 페이지 디자인 수정 #287

merged 25 commits into from
Nov 28, 2023

Conversation

hwinkr
Copy link
Collaborator

@hwinkr hwinkr commented Nov 27, 2023

🤠 개요

💫 설명

  • 꿀팁 페이지 디자인을 피그마에 있는 디자인으로 수정했어요.
  • 지난 회의에서 의견 나눈 것 처럼, 합성 컴포넌트를 최대한 적용하려고 했어요.
  • 추가로 render props를 적용해봤어요.

render props는 JSX.Element를 반환하는 함수를 전달하는 패턴이에요.
설명을 위해서 부림이 꿀팁 페이지에서 사용되는 바로가기 팁 카드, 꿀팁 팁 카드의 구성이 같지만 다르다고 가정해볼게요.

const TipCardList = ({category, articles}) => {
	if(category === "honeytip") return {articles.map(article => <HoneyTip .../>)}
    if(category === "shortcut") return {articles.map(article => <ShortCut .../>)}
	//...
} 

코드가 위와 같이 구성되어 있다면

  • 팁 카드의 유형이 많아질수록 분기 처리가 많아진다.
  • 팁 카드의 유형이 많아질수록 TipCardList 컴포넌트의 수정이 불가피해진다.
    이렇게 2가지 단점이 있어요. 이를 해결하기 위해서 **render props`를 적용해봤어요.
<TipCardList
tipList={tipList}
tipItemRenderer={(tipItem: TipData) => (
  <TipCard onClick={() => openLink(tipItem.link)}>
    <TipCard.TipTitle title={tipItem.title} />
    <TipCard.TipSubTitle subTitle={tipItem.subTitle} />
    <TipCard.TipImage
      title={tipItem.title}
      webpPath={tipItem.webpPath}
      pngPath={tipItem.pngPath}
    />
  </TipCard>
)}
/>
const TipCardList = ({ tipList, tipItemRenderer }: TipCardListProps) => {
  return (
    <Container>{tipList.map((tipItem) => tipItemRenderer(tipItem))}</Container>
  );
};

핵심은 url 경로에 따라 어떤 컴포넌트를 렌더링 할지 TipCardList내부에서 결정하는 것이 아니라 외부에서 결정하도록 해주는거에요. 이 방법을 사용하면

  • Close: 팁 카드 컴포넌트의 유형이 추가되더라도 TipCardList 컴포넌트의 수정은 필요 없어짐
  • Open: 팁 카드 컴포넌트의 유형이 추가되더라도 외부에서 JSX를 반환하는 함수를 주입하면 되기 때문에 확장에 유연해짐

따라서, OCP 원칙을 지킬 수 있게 돼요.

📷 스크린샷 (Optional)

- png, jpg보다 크기가 작아 이미지 최적화에 도움을 줄 수 있기 때문에 사용
- webp를 지원하지 않는 브라우저의 경우 png를 사용하도록 구현
- 바로가기, 꿀팁을 url로 구분하기 위해서 사용
- 팁 카드 제목, 부제목, 이미지를 children(React.ReactNode) props로 받음
- getTipCardSubElement에서 3개의 자식 컴포넌트 중 전달 된 컴포넌트를 확인
- 리액트에서 제공하는 Children API를 활용
- 전달받은 chidren들을 배열로 만든 후, 두번 째 인자로 전달한 childType과 일치하는 chidren들만 필터링해서 반환
- 공지사항, 취업 정보, 어학 정보, 꿀팁 페이지, 건의 사항 페이지 모두 상단 레이아웃의 구성이 비슷하기 때문에 최대한 재활용할 수 있도록 공통 컴포넌트로 구현하기로 결정
- 공지사항과 정보를 검색할 수 있는 검색 컴포넌트는 아직 구현하지 않음
- 일반, 고정 / 바로가기, 꿀팁 처럼 정보의 유형을 구분하는 책임을 가짐
- render props를 활용해서 어떤 컴포넌트를 사용해서 렌더링할 것인지를 외부에서 결정하도록 구현
- 이미 구현한 팁 카드 컴포넌트를 제외한 다른 컴포넌트를 사용해야 될 경우에 유연하게 대처할 수 있음(OCP)
Copy link
Member

@pp449 pp449 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 +14 to +27
<source srcSet={webpPath} type="image/webp" />
<Image
src={pngPath}
size="tiny"
alt={title}
css={css`
padding: 0 16px 16px 0;
position: absolute;
z-index: -1;
right: 0;
bottom: 0;
opacity: 0.2;
`}
/>
Copy link
Member

Choose a reason for hiding this comment

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

해당 코드는 webp 를 지원하는 브라우저면 webp 파일을 보여주고
그게 아니면 태그로 png를 보여주는 방식인가요?

분기처리가 없는걸로 봐서 2가지 방식의 이미지를 다 보여주는 방식인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

2개의 이미지를 다 보여주는 방식이 아니라, webp를 지원하는 브라우저면 webp를 사용해서 보여주고 그렇지 않다면 png 이미지를 보여주는 방식이에요. 여기 잘 정리되어 있어 도움 될 것 같아요~

Comment on lines +1 to 7
export interface TipData {
title: string;
subTitle: string;
webpPath: string;
pngPath: string;
link: string;
}
Copy link
Member

Choose a reason for hiding this comment

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

여기는 readonly가 없는 이유가 있나요?

Comment on lines +7 to +10
type InformUpperLayoutChildType =
| typeof InformTitle
| typeof InformSubTitle
| typeof InformTypeButton;
Copy link
Member

Choose a reason for hiding this comment

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

InformTitle, InformSubTitle, InformTypeButton
은 모두 function 타입으로 결국 같은 타입 아닐까요?
정상적인 함수 타입 체크가 가능할지 궁금하네요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image

리액트 함수형 컴포넌트에 typeof 연산자를 적용하면 이미지와 같이 어떤 props를 받아서 어떤 것을 반환하는지를 타입으로 정합니다. 함수의 콜 시그니쳐가 타입이 된다고 생각하면 될듯요. 그래서 정상적인 타입 체크가 가능해요

const Component = (props) => UI

따라서,

const add = (a: number, b: number) => a + b;

const TipCard = ({ children, onClick }: StrictPropsWithChildren) => {
  const tipTitle = getTipCardSubElement(children, TipTitle);
  const tipSubTitle = getTipCardSubElement(children, TipSubTitle);
  const tipImage = getTipCardSubElement(children, TipImage);

  const addFunciton = getTipCardSubElement(children, add)
  //...

add함수를 전달하면

Argument of type '(a: number, b: number) => number' is not assignable to parameter of type 'TipCardChildType'.
  Type '(a: number, b: number) => number' is not assignable to type '({ title }: TipTitleProps) => EmotionJSX.Element'.
    Target signature provides too few arguments. Expected 2 or more, but got 1.

이와 같은 에러가 발생하는 것을 알 수 있어요.

const childrenArray = Children.toArray(children);
const targetChild = childrenArray
.filter((child) => isValidElement(child) && child.type === childType)
.slice(0, 2);
Copy link
Member

Choose a reason for hiding this comment

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

왜 최대 2개까지 배열을 자르는지 궁금해요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  • 일반, 고정 구분 버튼
  • 바로가기, 꿀팁 구분 버튼

이렇게 합성 컴포넌트의 자식들이 최대 2개씩 있어서 일단 2개까지 잘랐어요. 나중에 구분하는 버튼들이 많아지고 자르는 컴포넌트의 갯수에도 제한을 두려고 한다면 함수 호출부에서 어디까지 자를 것인지를 주입할 수도 있겠네요~

Copy link
Member

Choose a reason for hiding this comment

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

props 로 받은 childType 에 매핑되는 함수가 언제 적용이 되는건가요?

흐름상 해당 함수에서 childType 으로 받은 함수를 적용하여 반환할 것 같은데 코드만 봤을때 잘 모르겠네요,,

Copy link
Member

@pp449 pp449 left a comment

Choose a reason for hiding this comment

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

코드를 이해하려고 한참 생각하면서 봤네요
힘들게 이해했지만 확장성이 좋을거 같다는 생각이 들어요
잘하시네요.. ㅎㅎㅎ

@hwinkr hwinkr merged commit aaa8138 into dev Nov 28, 2023
1 check passed
@hwinkr hwinkr deleted the style/#286 branch November 28, 2023 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Style: 꿀팁 페이지 디자인 수정
2 participants