๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿฟ Project

[SidePJ] ๊นƒํ—ˆ๋ธŒ Issue Cracker 3๋ถ€ - Recoil ์ƒํƒœ๊ด€๋ฆฌ์˜ ์‹œ์ž‘

by Tamii 2021. 7. 9.
๋ฐ˜์‘ํ˜•

๋“œ๋””์–ด ๋งˆ์ง€๋ง‰ ์ฃผ๊ฐ€ ์™”๋‹ค. 

์ด๋ฒˆ ๋ฏธ์…˜์˜ ๋งˆ์ง€๋ง‰ ์ฃผ์ด์ž , ์ฝ”๋“œ์Šค์ฟผ๋“œ์˜ ๋งˆ์ง€๋ง‰ ์ฃผ๋‹ค.. 

6๊ฐœ์›”๋™์•ˆ ์—ด์‹ฌํžˆ ๋‹ฌ๋ ธ๋Š”๋ฐ  ๋์ด ๋ณด์ด๋Š” ๊ฒƒ ๊ฐ™์•„ ๋„ˆ๋ฌด ์•„์‰ฝ๋‹ค.  (๋ฌผ๋ก  ๋‚˜์˜ ์‹ค๋ ฅ์€ ์•„์ง ๊ฐˆ๊ธธ์ด ๋ฉ€๋‹ค)

๊ทธ๋ž˜๋„ ํ™”์ดํŒ…ํ•˜๋ฉฐ ์ด๋ฒˆ์ฃผ ๋ฏธ์…˜ ์ตœ์„ ์„ ๋‹คํ•ด์„œ ๋ผ์ฟค๊ณผ ํ•จ๊ป˜ ๋๋‚ด๋ณผ ๊ฒƒ~!!!!!! ์•„Zใ…~~~~๐Ÿ”ฅ

 


๐Ÿ“ŒTypeScript์™€ Recoil์„ ํ™œ์šฉํ•œ ๋ชจ๋‹ฌ ์™ธ๋ถ€์˜์—ญ ํด๋ฆญ์‹œ ๋‹ซํž˜ ์ฒ˜๋ฆฌ

์‚ฌ์‹ค airbnb์—์„œ ํ–ˆ์–ด์„œ ๊ฐ„๋‹จํžˆ ๋  ๊ฑฐ๋ผ ์ƒ๊ฐํ–ˆ๋Š”๋ฐ ์ƒ๊ฐ๋ณด๋‹ค ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ๊ธธ์€ ํ—˜๋‚œํ–ˆ๋‹ค.

 

โ– ์›ํ•˜๋Š” ์ƒํ™ฉ 

+์•„์ด์ฝ˜ click : SideBarDrop ์—ด๋ฆผ

๋ชจ๋‹ฌ ์™ธ ์˜์—ญ click : SideBarDrop ๋‹ซํž˜

 

 

โ– ์˜ค๋ฅ˜ ์ƒํ™ฉ

Type '{ ref: RefObject<HTMLDivElement>; }' is not assignable to type 'IntrinsicAttributes'.
  Property 'ref' does not exist on type 'IntrinsicAttributes'.  TS2322

์ผ๋‹จ ๊ธฐ๋ณธ์ ์œผ๋กœ TS2322๋Š” ํƒ€์ž… ์˜ค๋ฅ˜

xx is not assignable to type ์€ props๋กœ ๋‚ด๋ฆฐ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๋•Œ, ๋‚˜์˜ค๋Š” ์—๋Ÿฌ๋ผ๊ณ  ํ•œ๋‹ค.

์ถœ์ฒ˜(https://kyounghwan01.github.io/blog/dev-report/error-report/React/#can-t-perform-a-react-state-update-on-an-unmounted-component)

 

ํ˜„์žฌ ์ƒํ™ฉ

const SideBar = (): JSX.Element => {

  const [isDropAsignee, setIsDropAsignee] = useRecoilState(dropAsigneeState);
  const dropDownElement = useRef<HTMLDivElement>(null);

  const dropAsigneeHandler = () => {
    setIsDropAsignee(!isDropAsignee);
  };

  useEffect(() => {
  
    const dropCloseHandler = (e: MouseEvent): void => {
    
      if (
        dropDownElement.current &&
        !dropDownElement.current.contains(e.target as Node)
      ) {
        setIsDropAsignee(false);
      }
    };
    
    document.addEventListener('mousedown', dropCloseHandler);
    return () => {
      document.removeEventListener('mousedown', dropCloseHandler);
    };
  }, []);


  return (
    <SideBarStyle>
      <SideBarCell>
        <SideBarTitle>
          <TextGroup type={T.SMALL} content={'๋‹ด๋‹น์ž'} color="#6E7191" />
          <CustomAddIcon onClick={() => dropAsigneeHandler()} />
          <div ref={dropDownElement}>{isDropAsignee && <SideBarDrop />}</div>
        </SideBarTitle>
     </SideBarCell>
     //์ƒ๋žต
   </SideBarStyle>
 )};  

+์•„์ด์ฝ˜ click  

isDropAsignee ๋ผ๋Š” ๋‹ด๋‹น์ž Drop์˜ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•œ ํ›„์—

dropAsigneeHandler ํ•จ์ˆ˜๋กœ ์ปจํŠธ๋กคํ•จ์œผ๋กœ์จ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.

 

--->๋ฌธ์ œ๋Š” ์™ธ๋ถ€ ์˜์—ญ ํด๋ฆญ์ด๋‹ค!

 

๋ชจ๋‹ฌ ์™ธ ์˜์—ญ click 

1) dropDownElement ๋กœ useRef ์ƒ์„ฑ  = <SideBarDrop> component์— ref

2) dropCloseHandler๋กœ useRef๊ฐ’๊ณผ ํด๋ฆญ ๊ฐ’์„ ๋น„๊ตํ•˜์—ฌ ๋ชจ๋‹ฌ ์™ธ ์˜์—ญ ํด๋ฆญ ์‹œ์—๋งŒ ๋‹ซํžˆ๋„๋ก ์„ค์ • 

 

 

โ– useRef์˜ ํƒ€์ž… 

const element = useRef<HTMLDivElement>(null);

์ถœ์ฒ˜ https://linguinecode.com/post/how-to-use-react-useref-with-typescript

 

โ– event์˜ type

const dropCloseHandler = (e: MouseEvent): void => {
    
      if (
        dropDownElement.current &&
        !dropDownElement.current.contains(e.target as Node)
      ) {
        setIsDropAsignee(false);
      }
    };

๋‹ค์Œ๊ณผ ๊ฐ™์ด type์€ ์•Œ๋งž๊ฒŒ ์„ค์ •ํ•ด ์ค„ ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, 

์กฐ๊ฑด๋ถ€ Component ์ž์ฒด์— ref๋ฅผ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ๋˜์ง€ ์•Š์•˜๋‹ค. 

 

ํŒ๋‹จ์œผ๋กœ๋Š” Modal์ด ์ƒ์„ฑ๋˜๊ธฐ ์ „์—๋„ useRef์ž์ฒด๊ฐ€ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์ž‡์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— 

ํด๋ฆญ์ด ๋˜์—ˆ์„๋•Œ๋‚˜ ๋ณด์—ฌ์ง€๋Š” SideBarDrop ์— useRef๋ฅผ ๊ฑธ์ง€ ์•Š๊ณ  ์ž๋ฆฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค. 

์ด๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ๋Š” ์ถ”๊ฐ€์ ์œผ๋กœ ๊ณต๋ถ€๊ฐ€ ํ•„์š”ํ•  ๋“ฏ ํ•˜๋‹ค

 

 

 //์ˆ˜์ • ์ „ 

{isDropAsignee && <SideBarDrop ref={dropDownElement}/>}


// ์ˆ˜์ • ํ›„

<SideBarDropDiv ref={dropDownElement}>
	{isDropAsignee && <SideBarDrop />}
</SideBarDropDiv>
       

๋”ฐ๋ผ์„œ ์ด์™€๊ฐ™์ดDiv๋กœ ๊ฐ์‹ธ๊ณ  Div์— ref ํ•จ์œผ๋กœ์จ ํ•ด๊ฒฐํ–ˆ๋‹ค!

 

 

์ฐธ๊ณ : https://close-up.tistory.com/entry/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%ED%8A%B9%EC%A0%95-%EC%98%81%EC%97%AD-%EC%99%B8-%ED%81%B4%EB%A6%AD-%EA%B0%90%EC%A7%80

https://www.pluralsight.com/guides/using-react-refs-typescript

 


๐Ÿ“Œ Recoil ๊ณต๋ถ€ํ•˜๊ณ  ์ ์šฉํ•˜๊ธฐ

 

Recoil์€ ๋‚ด๋ถ€์ ์œผ๋กœ ContextAPI๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Recoil๋กœ state๋ฅผ ์‚ฌ์šฉํ•  ์ปดํฌ๋„ŒํŠธ๋Š” Contextet๊ณต๊ธ‰์ž ์•ˆ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค.

RecoilAPI๋Š” ์—ฌ๋Ÿฌ๊ฐœ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๊ณ , ์ค‘์ฒฉ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค -> ์ด๋•Œ RecoilAPI๋Š” ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์กฐ์ƒ RecoilRoot์— ์ ‘๊ทผํ•œ๋‹ค.

์ฐธ๊ณ (https://taegon.kim/archives/10105)

 

Atom:

Recoil์˜ ๋‹จ์œ„ ๋ฐ์ดํ„ฐ

์Šคํ† ์–ด์— ์ €์žฅ๋˜๊ณ  ์ถ”์ถœ๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋ชจ๋‘ Atom์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ๋‹ค.

๊ณ ์œ ํ•œ key์™€ default(๊ธฐ๋ณธ๊ฐ’)์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

 

atom์€ ๊ฐ์ฒด, ๋ฐฐ์—ด ๋‹ค ๊ฐ€๋Šฅํ•จ

export const userState = atom({
  key: 'userState',
  default: null,
});
export const userState = atom({
  key: 'userState',
  default: {
  	id:1
    name:'tami'
  }
});

 

์ปดํฌ๋„ŒํŠธ๋Š” atom ๊ฐ์ฒด๋ฅผ ํ›…์— ์ „๋‹ฌํ•˜์—ฌ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ณ  ์„ค์ •ํ•œ๋‹ค.

useRecoilValue useRecoilState useSetRecoilState
state state, setState (useState์™€ ๋น„์Šท) setState

 

 

Selector 

๊ฐ€๊ณต๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜์—ฌ ์ €์žฅํ•˜๊ณ  ์‹ถ์„ ๋•Œ

๊ธฐ๋Šฅ์˜ ์‹คํ–‰ (๋น„๋™๊ธฐ ์•ก์…˜ ๊ฐ€๋Šฅ)

 


 

 

ํ˜„์žฌ ์ƒํ™ฉ์€ +๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ๋ฐ์ดํ„ฐ type์ด ๊ตฌ๋ถ„๋˜์ง€ ์•Š์•„ (๋‹ด๋‹น์ž/ ๋ ˆ์ด๋ธ”) ๋™์‹œ์— ๋‚ด๋ ค์˜ค๊ฒŒ ๋œ๋‹ค.

์ด๋Š” ํ˜„์žฌ ๋‹ด๋‹น์ž์˜ ๋‚ด๋ ค์˜จ ์ƒํƒœ (isDropAsignee)๋กœ๋งŒ ์กฐ๊ฑด์„ ์ค˜์„œ์ด๊ณ ,

<SideBarDropDiv ref={dropDownElement}>
            {isDropAsignee && (
              <SideBarDrop type={'๋‹ด๋‹น์ž'} assigneeData={userList} />
            )}
          </SideBarDropDiv>
<SideBarDropDiv ref={dropDownElement}>
            {isDropAsignee && (
              <SideBarDrop type={'๋ ˆ์ด๋ธ”'} labelData={labelList} />
            )}
</SideBarDropDiv>

์ด ์„ธ๊ฐ€์ง€ ์‚ฌ์ด๋“œ๋ฐ”์˜ DropDown์„ ๊ด€๋ฆฌํ•  ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘๊ฐ€์ง€ ์ด๋‹ค.

1) 3๊ฐ€์ง€์˜ Dropdwon ์ƒํƒœ๋ฅผ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ 

isDropAisgnee , isDropLabel, isDropMileston  

 

2)  3๊ฐ€์ง€์˜ Dropdwon ์ƒํƒœ๋ฅผ ๊ฐ์ฒด๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ 

atom์—์„œ dropState๋ฅผ ๊ฐ์ฒด๋กœ ๊ด€๋ฆฌํ•œ ํ›„ 

๊ฐ ํด๋ฆญ ์‹œ selecotr๋กœ dropState๋ฅผ ๋ณ€ํ™”์‹œํ‚ค๋Š” ๋ฒ•

 

--> Dropdown์˜ ์ƒํƒœ๋ฅผ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ๋‹ค.

export const dropAssigneeState = atom({
  key: 'dropAssigneeState',
  default: false,
});
export const dropLabelState = atom({
  key: 'dropLabelState',
  default: false,
});
export const dropMilestoneState = atom({
  key: 'dropMilestoneState',
  default: false,
});

 

 

 


๐Ÿ“Œ Recoil ๋น„๋™๊ธฐ  ์™€ suspense

Error: IssueTable suspended while rendering, but no fallback UI was specified. Add a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.

 

 <React.Suspense fallback={null}>
        <IssueTable />
      </React.Suspense>

Recoil์€ ๋ณด๋ฅ˜์ค‘์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด React Suspense ์™€ ํ•จ๊ป˜ ๋™์ž‘ํ•˜๋„๋ก ๋””์ž์ธ๋˜์–ด์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋ฅผ Suspense ๊ฒฝ๊ณ„๋กœ ๊ฐ์‹ธ์„œ ์•„์ง ๋ณด๋ฅ˜์ค‘์ธ ํ•˜์œ„ ํ•ญ๋ณต๋“ค์„ ์žก์•„๋‚ด๊ณ  ๋Œ€์ฒดํ•˜๊ธฐ ์œ„ํ•œ UI๋ฅผ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ˜„์žฌ๋Š” null๊ฐ’์„ ๋„ฃ์–ด๋†“์•˜๋‹ค.

 

์ฐธ๊ณ  :https://recoiljs.org/ko/docs/guides/asynchronous-data-queries/

 

๐Ÿ“Œ ์ง€์ • ๊ฐ์ฒด type์„  ๋ฐ›์•„์˜ฌ ๋–„ 

 

const IssueTableHeader = ({
  IssueData,
}: {
  IssueData: IssueDataProps;
}): JSX.Element => {

const IssueTableHeader = ({IssueData}: IssueDataProps}):
export interface IssueDataProps {
  
    issueId: number;
    milestoneInfo: {
      description: string;
      dueDate: string;
      title: string;
    };
    title: string;
    content: string;
    status: string;
    writer: AssigneeProps[];
    createdDateTime: string;
    assignees: AssigneeProps[];
    labels: LabelProps[];
  
}

 

 

Json ํ˜•ํƒœ์˜ Type ์„ค์ •

type์˜ ํ˜•ํƒœ๊ฐ€ json ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ๋˜์–ด ์žˆ์œผ๋ฉด ํƒ€์ž… ์ง€์ •์‹œ ๋‘๊ฐ€์ง€๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ,

์˜ˆ์‹œ๋กœ ProfileProps ๊ฐ€ ์ด์™€ ๊ฐ™์ด ์ƒ๊ฒผ์„ ๋•Œ ์‚ฌ์šฉ ์‹œ์—๋Š” 

 

1) ์‚ฌ์šฉ ์‹œ ๊ฐ์ฒด ํ• ๋‹น

export interface nameProps {
    		first: string,
            last : string,
}
const ProfileComponent = ({name }: {name : ProfileProps}): JSX.Element => {

 

 

2) Type , interface ๋””ํ…Œ์ผํ•˜๊ฒŒ ์ง€์ •ํ•˜๊ธฐ  

export interface nameProps {
	name:{
    		first: string,
            last : string,
         }
}
const ProfileComponent = ({name }: ProfileProps): JSX.Element => {

์ž์œ ๋„๊ฐ€ ๋” ๋†’์€ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ๋‹ค. 

 

 

๐Ÿ“Œ Typescript์—์„œ ๋‚ ์งœ ๊ณ„์‚ฐ ์‹œ ์˜ค๋ฅ˜

The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. TS2362

 

๋‚ ์งœ๊ณ„์‚ฐ์„ ํ• ๋•Œ ๋‚ ์งœ๋ผ๋ฆฌ ์—ฐ์‚ฐํ•  ๋•Œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—์„œ๋Š ๋ช…์‹œ๋ฅผ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

http://ccambo.github.io/Dev/Typescript/1.typescript-problem-solving-and-tips/

 

๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ์‹œ๊ฐ„ ๋ช‡๋ถ„ ์ „์œผ๋กœ ๋‚˜ํƒ€๋‚ด๋Š” ํ•จ์ˆ˜

export const getElapsedTime = (date: string): string => {
  const createdTime = new Date(date);
  const current = new Date();
  const gapMin = Math.floor((+current - +createdTime) / 1000 / 60);

  if (gapMin < 1) return '๋ฐฉ๊ธˆ ์ „';

  if (gapMin < 60) return `${gapMin}๋ถ„ ์ „`;

  const gapHour = Math.floor(gapMin / 60);
  if (gapHour < 24) return `${gapHour}์‹œ๊ฐ„ ์ „`;

  const gapDay = Math.floor(gapHour / 24);
  if (gapDay < 30) return `${gapDay}์ผ ์ „`;

  const gapMonth = Math.floor(gapDay / 12);
  if (gapMonth < 12) return `${gapMonth}๋‹ฌ ์ „`;

  return '๋ช‡ ๋…„ ์ „';
};

์ด๋ ‡๊ฒŒ util ํ•จ์ˆ˜์— ๋„ฃ์–ด ๋ช‡ ๋ถ„ ์ „์— ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋Œ“๊ธ€