๐Ÿฌ ๊ธดํ˜ธํก/๊ณ ์Šค๋ฝ ํ‹ฐ์ผ“

[๋‘๋‘ฅ] ์„ ์–ธ์ ์ธ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ

ํ•œ๊ทœ์ง„ 2023. 3. 30. 11:39

๊ฐœ๋ฐœ์„ ํ• ๋•Œ ์ƒˆ๋กœ์šด ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งž๋‹ฅ๋œจ๋ฆฌ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ค„์–ด๋“ค์–ด์„œ ๊ทธ๋Ÿฐ๊ฐ€, ์ฝ”๋“œ๋ฅผ ์ด์˜๊ฒŒ ์งœ๋Š” ๊ฒƒ์— ๊ด€์‹ฌ์ด ๋งŽ์•„์กŒ๋‹ค. ๋‘๋‘ฅ์„ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๋งˆ์ฃผ์นœ ๊ณ ๋ฏผ๋“ค์„ ๊ณต์œ ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค. ๋ฆฌ์•กํŠธ๋กœ ๊ฐœ๋ฐœํ•˜๋‹ค๋ณด๋ฉด ์„ ์–ธ์ ์ธ ์ฝ”๋“œ์— ๋Œ€ํ•ด์„œ ๊ณ ๋ฏผํ•˜๊ฒŒ ๋œ๋‹ค. ๋ฆฌ์•กํŠธ ์ž์ฒด๊ฐ€ ์„ ์–ธํ˜•์ด๊ธฐ ๋•Œ๋ฌธ์ผ์ˆ˜๋„.

 

์„ ์–ธ์ ์ธ ์ฝ”๋“œ

๋ช…๋ นํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ๊ณผ ์„ ์–ธํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์— ๋Œ€ํ•œ ๊ธ€๋“ค์—์„  ํ”ํžˆ 'How'์™€ 'What'์˜ ์ฐจ์ด๋กœ ์„ค๋ช…ํ•œ๋‹ค.

 

<ul id=”list”></ul>
<script>
var arr = [1, 2, 3, 4, 5]
var elem = document.querySelector("#list");

for(var i = 0; i < arr.length; i ++) {
  var child = document.createElement("li");
  child.innerHTML = arr[i];
  elem.appendChild(child);
}
</script>

๋ฐ˜๋ณต๋ฌธ์„ ํ†ตํ•ด ๋ฐฐ์—ด์˜ ์›์†Œ๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ html์š”์†Œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ณด์—ฌ์ฃผ๋Š” ์ฝ”๋“œ์ด๋‹ค. ์–ด๋–ค ์ ˆ์ฐจ๋กœ ์ด๋ฃจ์–ด์ง€๋Š”์ง€๊ฐ€ ๋“œ๋Ÿฌ๋‚˜ ์žˆ๋‹ค.

 

const arr = [1, 2, 3, 4, 5];
return (
  <ul>
    {arr.map((elem) => (
      <li>{elem}</li>
    ))}
  </ul>
);

React์—์„œ jsx ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•ต์‹ฌ ๋ฐ์ดํ„ฐ๋งŒ ์™ธ๋ถ€์—์„œ ์ „๋‹ฌ ๋ฐ›๊ณ  ์„ธ๋ถ€์ ์ธ ๊ตฌํ˜„์€ mapํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ˆจ๊ฒจ์ ธ์žˆ๋‹ค. ๋ณต์žกํ•œ ์ž‘์—…์„ ์ถ”์ƒํ™”ํ–ˆ๋‹ค๊ณ  ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ•œ๋‹จ๊ณ„ ๋” ์ถ”์ƒํ™”ํ•œ๋‹ค๋ฉด

<NumberListItem data={arr}/>

ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋Œ์•„๊ฐ€๋Š”์ง€๋Š” ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๊ณ , ๋ฌด์—‡์„ ๋ณด์—ฌ์ค„์ง€๋งŒ ์ „๋‹ฌํ–ˆ๋‹ค. ๋”์šฑ ๋น ๋ฅด๊ฒŒ ์ฝ”๋“œ์˜ ์—ญํ• ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

 

ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์„ ํ•  ๋•Œ๋Š” ์„ ์–ธ์ ์ธ ์ฝ”๋“œ๋ฅผ "์ถ”์ƒํ™” ๋ ˆ๋ฒจ์ด ๋†’์•„์ง„ ์ฝ”๋“œ"๋กœ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฌด์กฐ๊ฑด ๋†’๋‹ค๊ณ  ์ข‹์€ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๊ตฐ๋ฐ์—์„œ ์žฌํ™œ์šฉ๋˜๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŽ˜์ด์ง€์— ์•ฝ๊ฐ„์˜ ์ˆ˜์ •์ด ํ•„์š”ํ• ๋•Œ ์ข…์ข… ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค. ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ์กฐ๊ฑด์„ ๊ฑธ๊ณ  prop์„ ๋”ํ•˜๋‹ค ๋ณด๋ฉด, ์˜คํžˆ๋ ค ์ฑ…์ž„์ด ๋งŽ์•„์ง€๊ณ  ๋„ค์ด๋ฐ์ด ๋ชจํ˜ธํ•ด์ง€๊ธฐ๋„ ํ•œ๋‹ค. ๋•Œ๋ฌธ์— ์ถ”์ƒํ™”์˜ ๋ ˆ๋ฒจ์„ ์ ์ ˆํžˆ ์„ ํƒํ•ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

 


 

๋‘๋‘ฅ์—๋„ ์„ ์–ธ์ ์ธ ์ฝ”๋“œ๊ฐ€ ์ž์ฃผ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค. ์ด์ „ ๋‘๋ฒˆ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ด์–ด์˜ค๋ฉด์„œ ๋งŽ์€ ๊ธฐ๋Šฅ์ด ๊ทธ๋Œ€๋กœ ๋‘๋‘ฅ์œผ๋กœ ๋“ค์–ด์™”๋‹ค. ๊ทธ ๋•Œ๋ถ€ํ„ฐ ์—ผ๋‘์— ๋‘๋˜ ๋ฆฌํŒฉํ† ๋ง์„ ํ•˜๋Š” ๊ธฐ๋ถ„์œผ๋กœ ๊ฐœ๋ฐœ์„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋ช‡๊ตฐ๋ฐ ์†Œ๊ฐœํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

๋ฌดํ•œ ์Šคํฌ๋กค

๋ฆฌํŒฉํ† ๋ง ์ด์ „

๊ธฐ์กด ๊ณ ์Šค๋ฝ ํ‹ฐ์ผ“์—์„œ ์‘์›ํ†ก์€ ๋ฌดํ•œ์Šคํฌ๋กค๋กœ ๋ณด์—ฌ์ง„๋‹ค. ์ด๋ ‡๊ฒŒ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์—ฐ์†์ ์œผ๋กœ ๋ฐ›์•„์˜ค๊ณ  ์ ์ ˆํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ Œ๋”๋งํ•˜๋Š” ๋™์ž‘์„ ์ถ”์ƒํ™”ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

 

React Query์˜ useInfiniteQuery๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ๋ฌดํ•œ์Šคํฌ๋กค ๋ฐ์ดํ„ฐ๋“ค์„ ๊ฐ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ๋“ค์˜ ๋ฐฐ์—ด๋กœ ๋ฐ›์•„์˜ค๋„๋ก ์ถ”์ƒํ™”๋˜์–ด์žˆ๋‹ค. ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•จ์ˆ˜์˜ ์‚ฌ์šฉ๋ฒ• ์ž์ฒด๋Š” ์œ„ ๊ธ€์— ์ •๋ฆฌ๋˜์–ด์žˆ๋‹ค.

 

const TalkList = ({ talkList }: { talkList: ITalk[] }) => {
  return (
    <Wrapper>
      {talkList.map((talk) => (
        <TalkBubble
          nickName={talk.nickName}
          content={talk.content}
          createdAt={talk.createdAt}
          iComment={talk.iComment}
          key={talk.id}
        />
      ))}
    </Wrapper>
  );
};
<TalkListWrapper isOpen={isOpen} ref={talkListRef}>
  {data?.pages.map((talkList) => (
    <TalkList talkList={talkList.talkList} key={talkList.lastId} />
  ))}
  <Observation />
</TalkListWrapper>

๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋“ค์€ ์œ„์™€๊ฐ™์ด ๋‘๋ฒˆ์˜ map์„ ํ†ตํ•ด ๋ Œ๋”๋ง๋œ๋‹ค. ๊ฐ ํŽ˜์ด์ง€๋งˆ๋‹ค ํ•œ๋ฒˆ, ๊ทธ ์•ˆ์—์„œ ํ•œ๋ฒˆ. useInfiniteQuery์™€ mapํ•จ์ˆ˜์˜ ๋„์›€์œผ๋กœ ์ด๋ฏธ ์–ด๋Š์ •๋„ ์ถ”์ƒํ™”๋˜์–ด ์žˆ๋Š” ์ฝ”๋“œ์ด๋‹ค.

 

๋ฆฌํŒฉํ† ๋ง ์ดํ›„

๋‘๋‘ฅ์—์„œ๋Š” ์‘์›ํ†ก ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๊ณต์—ฐ, ํ˜ธ์ŠคํŠธ, ๋ฉค๋ฒ„ ๋ชฉ๋ก ๋“ฑ์—์„œ ๋ฌดํ•œ์Šคํฌ๋กค์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ๋งŽ์•„์กŒ๋‹ค. ๋ฌดํ•œ์Šคํฌ๋กค ํŽ˜์ด์ง€๋ฅผ ์œ„ํ•ด ๋งค๋ฒˆ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‘๊ฐœ์”ฉ ๋งŒ๋“ค์–ด ์“ฐ๋Š”๊ฑด ๋ถˆํŽธํ•˜๋‹ค. ํ•œ๋‹จ๊ณ„ ๋” ์ถ”์ƒํ™”ํ•ด๋ณธ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

const { infiniteListElement } = useInfiniteQueriesList<EventResponse>(
    ['events', keyword],
    ({ pageParam = 0 }) =>
      EventApi.GET_EVENTS_SEARCH({ keyword, pageParam, size: 12 }),
    EventLink,
  );

useInifiniteQueriesList๋ผ๋Š” Hook์ด๋‹ค. ์ฟผ๋ฆฌํ‚ค, api ํ˜ธ์ถœ ํ•จ์ˆ˜(QueryFn), ๋ Œ๋”๋งํ•  ์•„์ดํ…œ(ListItem), ์ด๋ ‡๊ฒŒ 3๊ฐœ์˜ ํ•ต์‹ฌ์ •๋ณด๋งŒ ์ „๋‹ฌํ•œ๋‹ค. ๋ฌดํ•œ์Šคํฌ๋กค๋กœ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ์™€ ListItem์„ ์ด์šฉํ•ด infiniteListElement๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

<EventList>{infiniteListElement}</EventList>

์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

์ „์ฒด Hook์˜ ์ฝ”๋“œ์ด๋‹ค. observer์—์„œ ์Šคํฌ๋กค์˜ ๋์„ ์ฐพ์œผ๋ฉด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ํŒจ์นญํ•ด์˜ค๊ณ , map์œผ๋กœ ListItem ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ํ๋ฆ„์ด ์ˆจ๊ฒจ์ ธ์žˆ๋‹ค.

export const useInfiniteQueriesList = <T,>(
  queryKey: QueryKey,
  apiFunction: (payload: any) => Promise<InfiniteResponse<T>>,
  ListItem: (props: any) => JSX.Element,
  options?: UseInfiniteQueryOptions<
    InfiniteResponse<T>,
    AxiosError,
    InfiniteResponse<T>,
    InfiniteResponse<T>,
    QueryKey
  >,
) => {
  const { data, fetchNextPage } = useInfiniteQuery<
    InfiniteResponse<T>,
    AxiosError
  >(queryKey, apiFunction, {
    getNextPageParam: (lastPage) => lastPage.page + 1,
    ...options,
  });         
  const [ref, inView] = useInView();
  /* observer ๊ด€๋ จ... */
  const observer = (
    <div className="observer" ref={ref} style={{ height: '1px' }} />
  );

  const listElement = data?.pages.map(({ content }) =>
    content.map((item, idx) => <ListItem {...item} key={`item-${idx}`} />),
  );

  const isEmpty = data?.pages[0].content.length === 0;

  return {
    infiniteListElement: (
      <>
        {listElement}
        {observer}
      </>
    ),
    isEmpty,
  };
};

ํ•ด๋‹น Hook์„ ์ด์šฉํ•ด ๋‹ค์–‘ํ•œ ํŽ˜์ด์ง€์—์„œ ๋ฌดํ•œ์Šคํฌ๋กค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๋„ค๋ฆญ์„ ์ด์šฉํ•˜๊ณ  ์žˆ๋‹ค. QueryFnData์˜ ํƒ€์ž…์œผ๋กœ InfiniteResponse<T>๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ  ์žˆ๋‹ค.

 

export interface InfiniteResponse<T> {
  content: T[];
  page: number;
  size: number;
  hasNext: boolean;
}

๋ฌดํ•œ์Šคํฌ๋กค๋กœ ๋ฐ›์•„์˜ค๋Š” ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์€ ๋ชจ๋‘ ์œ„์™€ ๊ฐ™์ด ํ†ต์ผ๋˜์–ด ์žˆ๋‹ค. content๋กœ ๋‹ค์–‘ํ•œ ํƒ€์ž…์˜ ๊ฐ’์ด ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ์ฟผ๋ฆฌ ์˜ต์…˜๋„ ์˜ต์…”๋„๋กœ ๋ฐ›๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž์œ ๋กญ๊ฒŒ ๋„ฃ์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์‹ค์ œ๋กœ ์‘์›ํ†ก์€ 2์ดˆ๋งˆ๋‹ค pullingํ•˜๋„๋ก ํ•˜๋Š” ์˜ต์…˜์ด ์„ค์ •๋˜์–ด ์žˆ๋‹ค.

 

Overlay ์ปดํฌ๋„ŒํŠธ

๋ชจ๋‹ฌ์ฐฝ ์—ญ์‹œ ๊ณ ์Šค๋ฝ ํ‹ฐ์ผ“์—์„œ๋ถ€ํ„ฐ ๊ณ„์† ์จ์˜ค๋˜ UI์˜€๋‹ค. ๋‘๋‘ฅ์„ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๋ชจ๋ฐ”์ผ์ผ ๋• ํ™”๋ฉด ์•„๋ž˜์—์„œ ์˜ฌ๋ผ์˜ค๋Š”๊ฒŒ ๋” ์ž์—ฐ์Šค๋Ÿฝ๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ ๋ฐ”ํ…€์‹œํŠธ๋กœ, PC์—์„  ๋ชจ๋‹ฌ๋กœ ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ๋„์šฐ๋„๋ก ํ–ˆ๋‹ค.

const { isOpen, openOverlay, closeOverlay } = useOverlay();

<OverlayBox open={isOpen} onDismiss={closeOverlay}>
  <SelectTicket items={tickets?.ticketItems} eventName={detail.name} />
</OverlayBox>

 

Popup ์ปดํฌ๋„ŒํŠธ

์–ด๋“œ๋ฏผํŽ˜์ด์ง€์—์„œ ํ…Œ์ด๋ธ” ๋ฉ”๋‰ด๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ๋œจ๋Š” ํŒ์—… ๋ฉ”๋‰ด ์ปดํฌ๋„ŒํŠธ์ด๋‹ค. ์ด ์™ธ์—๋„ ํ—ค๋”์˜ ํ”„๋กœํ•„์„ ํด๋ฆญํ–ˆ์„ ๋•Œ, ๊ฒ€์ƒ‰ ์˜ต์…˜์„ ๋ณ€๊ฒฝํ•  ๋•Œ ํŒ์—… ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

<Popup options={approveWaitingOptions}>
    <Icon name="threeDot" />
</Popup>;

๊ทธ๋Ÿด ๋•Œ Popup ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. children์œผ๋กœ ํŒ์—… ๋ฒ„ํŠผ์œผ๋กœ ์‚ฌ์šฉํ•  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋„ฃ์–ด์ค€๋‹ค. ์–ด๋–ค ์˜ต์…˜์„ ๋ณด์—ฌ์ค„์ง€ PopupOptions[] ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋์ด๊ธฐ ๋•Œ๋ฌธ์— ์„ ์–ธ์ ์ธ ์ฝ”๋“œ๋กœ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

const approveWaitingOptions: PopupOptions[] = [
  {
    text: '์Šน์ธํ•˜๊ธฐ',
    onClick: () => {
      approveMutate({ eventId, order_uuid: data.orderUuid });
    },
  },
  {
    text: '์ž์„ธํžˆ ๋ณด๊ธฐ',
    onClick: () => {
      openOverlay({
        content: 'tableViewDetail',
        props: { eventId, order_uuid: data.orderUuid },
      });
    },
  },
];

์˜ˆ๋ฅผ ๋“ค์–ด, approveWatingOptions๋Š” text์™€ onClick์„ ์†์„ฑ์œผ๋กœ ๊ฐ–๋Š” ๊ฐ์ฒด์˜ ๋ฐฐ์—ด์ด๋‹ค. ์Šน์ธ ๋Œ€๊ธฐ์ค‘์ธ ์ฃผ๋ฌธ์˜ ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์ฃผ๋ฌธ์„ ์Šน์ธํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ์œ„์—์„œ ์ž‘์„ฑํ–ˆ๋˜ useOverlay Hook์˜ openOverlay ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๋ชจ๋‹ฌ์„ ์—ฌ๋Š” ์˜ต์…˜์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค!

 


 

 

https://sangmin802.github.io/Study/Think/abstract%20painting/
https://toss.tech/article/frontend-declarative-code
https://toss.im/slash-21/sessions/3-3