λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

React

[React] λ¦¬μ•‘νŠΈ μ„œμŠ€νŽœμŠ€?! (React Suspense)

졜근 λΈ”λ‘œκ·Έ 글을 읽어보닀 λ¦¬μ•‘νŠΈ μ„œμŠ€νŽœμŠ€λΌλŠ” κ°œλ…μ„ μ•Œκ²Œ λ˜μ—ˆλ‹€. μ΄μ „κΉŒμ§€ μ§„ν–‰ν–ˆλ˜ ν”„λ‘œμ νŠΈ 및 μ•žμœΌλ‘œ 진행할 ν”„λ‘œμ νŠΈμ— μ μš©ν•˜λ©΄ 쒋을 것이라고 생각해 μ•Œμ•„λ³΄κ²Œ λ˜μ—ˆλ‹€.

 

🧐 React Suspense λž€?

λ¦¬μ•‘νŠΈμ˜ μ„œλΈŒνŽœμŠ€λŠ” 16.6 λ²„μ „μ—μ„œ μ‹€ν—˜μ (experimental) κΈ°λŠ₯으둜 μΆ”κ°€λ˜μ—ˆκ³  18버전뢀터 κ³΅μ‹μ μœΌλ‘œ 정식 κΈ°λŠ₯으둜 μ§€μ›ν•˜κΈ° μ‹œμž‘ν–ˆλ‹€. 이 μ„œμŠ€νŽœμŠ€λ₯Ό μ΄μš©ν•˜λ©΄ μ–΄λ–€ μž‘μ—…μ΄ μ’…λ£Œλ˜κΈ° 전에 μ»΄ν¬λ„ŒνŠΈμ˜ λžœλ”λ§μ„ μ€‘λ‹¨μ‹œμΌœ λ™μ‹œμ— λžœλ”λ§μ΄ 되게 ν•œλ‹€λ˜μ§€, λ‹€λ₯Έ μ»΄ν¬λ„ŒνŠΈμ˜ λžœλ”λ§μ„ μ•ž μˆœμ„œλ‘œ 이끌 수 μžˆλ‹€. 

 

예λ₯Ό λ“€μ–΄ (λΆ€λͺ¨ μ»΄ν¬λ„ŒνŠΈ - μžμ‹ μ»΄ν¬λ„ŒνŠΈ1 - μžμ‹ μ»΄ν¬λ„ŒνŠΈ2) 처럼 μ»΄ν¬λ„ŒνŠΈλ“€μ΄ μ‘΄μž¬ν•  λ•Œ λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈκ°€ 비동기 μž‘μ—…μ„ ν¬ν•¨ν•˜κ³  μžˆλ‹€κ³  ν•œλ‹€λ©΄ λΆˆλΆ„λͺ…ν•œ 비동기 λ™μž‘μ˜ μˆ˜ν–‰ μ’…λ£Œ μ‹œμ μœΌλ‘œ 인해 λΆ€λΆ„μ μœΌλ‘œ 일뢀씩 λžœλ”λ§λ  수 μžˆλ‹€. ν•˜μ§€λ§Œ μ„œμŠ€νŽœμŠ€ κΈ°λŠ₯을 μ΄μš©ν•˜λ©΄ ν•œλ²ˆμ— 보여쀄 수 μžˆλ‹€.

 

λ‚΄κ°€ 직접 κ²½ν—˜ν–ˆλ˜ 것을 μ˜ˆμ‹œλ‘œ 듀어보면 이전에 "λ¨ΉλŒ€μž₯" ν”„λ‘œμ νŠΈλ₯Ό μ§„ν–‰ν–ˆλ˜ 적이 μžˆλ‹€.(https://mukdaejang-3b8e6.web.app/bestRestaurants/Seocho-Food) μ΄λ•Œ μŠ€μΌˆλ ˆν†€ UIκ°€ λ“±μž₯ν•˜κ³  데이터가 λ‘œλ“œλ˜λ©΄ ν•˜λ‚˜λ‘˜μ”© λ³΄μ—¬μ€˜μ•Ό ν•˜λŠ” μ •λ³΄λ“€λ‘œ λŒ€μ²΄κ°€ λ˜λŠ”λ° μˆœμ„œκ°€ 뒀죽박죽이닀. 여기에 μ„œμŠ€νŽœμŠ€ κΈ°λŠ₯을 μ‚¬μš©ν•œλ‹€λ©΄ λ™μ‹œμ— 보여지며 UXλ₯Ό λ”μš± 높일 수 μžˆμ„ κ²ƒμ΄λΌλŠ” 생각을 ν–ˆλ‹€. 

 

μ„œμŠ€νŽœμŠ€λ₯Ό μ΄μš©ν•˜λ©΄ λžœλ”λ§ μ‹œκΈ°λ₯Ό μ‘°μ ˆν•  수 μžˆλ‹€. 

μ»΄ν¬λ„ŒνŠΈκ°€ μ‹€ν–‰λ˜λŠ” μˆœμ„œλ₯Ό μ‘°μ ˆν•΄μ„œ λžœλ”λ§ μˆœμ„œλ₯Ό 쑰절 ν•  수 μžˆλŠ” 것을 κΈ°λŒ€ν–ˆμœΌλ‚˜ κ·ΈλŸ°κ²ƒμ€ μ•„λ‹ˆμ—ˆλ‹€.

 

μƒμœ„ μ»΄ν¬λ„ŒνŠΈμ—μ„œ 비동기 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ³  λ‘œλ“œλœ 데이터 λ˜λŠ” λ¦¬μ†ŒμŠ€λ₯Ό ν•˜μœ„ μ»΄ν¬λ„ŒνŠΈλ‘œ μ „λ‹¬ν•œλ‹€. μ΄λ•Œ μžμ‹ μ»΄ν¬λ„ŒνŠΈμ—μ„œ 아직 λ‘œλ“œλ˜μ§€ μ•Šμ€ λ¦¬μ†ŒμŠ€λ₯Ό ν‘œμ‹œν•΄μ•Ό ν•˜λŠ” 경우 'Suspense' κ΅¬μ„±μš”μ†Œλ₯Ό μ‚¬μš©ν•˜μ—¬ λ¦¬μ†ŒμŠ€κ°€ λ‘œλ“œλ˜λŠ” λ™μ•ˆ ν‘œμ‹œν•  λŒ€μ²΄ μ»΄ν¬λ„ŒνŠΈλ₯Ό 지정할 수 μžˆλ‹€.

function App() {

  return (
    <div>
      <Suspense fallback={<Loading />}>
        <Data resource={getCatImg()} />
      </Suspense>
    </div>
  );
}

fallbackμ΄λΌλŠ” ν”„λ‘œνΌν‹°λ₯Ό μ‚¬μš©ν•΄μ„œ λŒ€μ²΄ν•  μ»΄ν¬λ„ŒνŠΈ ν˜Ήμ€ λ‚΄μš©μ„ 보여쀄 수 μžˆλ‹€. DataλΌλŠ” μžμ‹ μ»΄ν¬λ„ŒνŠΈμ—λŠ” resourceλΌλŠ” prop으둜 getCatImgλΌλŠ” ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μ΄ μ „λ‹¬λ˜λŠ”λ° getCatImg ν•¨μˆ˜μ˜ λ‚΄μš©μ€ λ‹€μŒκ³Ό κ°™λ‹€.

 

const getCatImg = () => {
  let data = null;
  const suspender = fetch('https://api.thecatapi.com/v1/images/search?limit=10')
    .then(response => response.json())
    .then(d => {
        data = d;
    });

  return {
    read() {
      if (data === null) {
        throw suspender;
      } else {
        return data;
      }
    },
  };
};

getCatImg ν•¨μˆ˜λŠ” 고양이 사진에 λŒ€ν•œ 정보λ₯Ό λ‹΄κ³  μžˆλŠ” 정보λ₯Ό fetch ν•œλ‹€. 이후 ν”„λ‘œλ―ΈμŠ€μ˜ Pendingμ΄λ‚˜ Fulfilled 여뢀에 상관없이 κ²½μš°μ— 따라 λ‹€λ₯Έ λ™μž‘μ„ ν•˜λŠ” readλΌλŠ” λ©”μ„œλ“œλ₯Ό 가진 객체λ₯Ό λ°˜ν™˜ν•œλ‹€. 

 

function Data({ resource }) {
  const data = resource.read();

  return (
    <section className="container">
      <h2 className="title">Numbers Box</h2>
      <ul>
        {data.map((item, i) => (
          <li key={i}>
            <img className="img" src={item.url} alt="" />
          </li>
        ))}
      </ul>
    </section>
  );
}

Data μ»΄ν¬λ„ŒνŠΈλŠ” μœ„μ™€ 같이 ꡬ성이 λ˜μ–΄ μžˆλŠ”λ° λΆ€λͺ¨μ—κ²Œμ„œ 받은 resource의 read λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•΄ μƒνƒœλ₯Ό 확인할 수 μžˆλ‹€. μ΄λ•Œ λ§Œμ•½ Fulfilled λ˜μ–΄ 데이터가 μ •μƒμ μœΌλ‘œ λ‘œλ“œλ˜μ—ˆλ‹€λ©΄ Data μ»΄ν¬λ„ŒνŠΈκ°€ λžœλ”λ§λ˜κ² μ§€λ§Œ 아직 pending μƒνƒœλΌλ©΄ Suspense의 fallback ν”„λ‘œνΌν‹°μ— λ“€μ–΄κ°„ λ¦¬μ•‘νŠΈ λ…Έλ“œλ‘œ λŒ€μ²΄λœλ‹€. λ§Œμ•½ μž‘μ—…μ΄ μ‹€νŒ¨(Rejected)ν•œλ‹€λ©΄ μ–΄λ–»κ²Œ 될까? μ΄λ•ŒλŠ” ErrorBoundaryλ₯Ό μ‚¬μš©ν•˜μ—¬ μ—λŸ¬μƒνƒœλ₯Ό ν¬μ°©ν•˜κ³  fallback의 λ¦¬μ•‘νŠΈ λ…Έλ“œλ‘œ λŒ€μ²΄ν•  수 μžˆλ‹€.

 

function App() {

  return (
    <div>
      <ErrorBoundary fallback={<Error />}>
        <Suspense fallback={<Loading />}>
          <Data resource={getCatImg()} />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

 

λ¦¬μ•‘νŠΈμ˜ κΈ°μ‘΄ 방식을 μ‚΄νŽ΄λ³΄λ©΄ ν•˜λ‚˜μ˜ μ»΄ν¬λ„ŒνŠΈκ°€ 컀질 수 μžˆλ‹€. μœ„μ—μ„œ μ„€λͺ…ν•œ μ½”λ“œμ˜ κΈ°μ‘΄ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄λ©΄ λ‹€μŒκ³Ό 같은 μ½”λ“œλ“€μ΄ μ‘΄μž¬ν•  것이닀.

function App() {
  const [isLoaded setIsLoaded] = useState(false);
  const [data setData] = useState();

  useEffect(() => {
    const getCatImg = async () => {
      const res = await fetch('https://api.thecatapi.com/v1/images/search?limit=9');
      const data = await response.json();
      return data;
    }

    setData(getCatImg());
    setIsLoaded(true); 
  }, []);

  return (
    <div>
      { isLoaded ? <Data data={data} /> : <Loading /> }
    </div>
  );
}

 

 

μ΄λ•Œ λ‘œλ“œκ°€ λ¬λŠ”μ§€λ₯Ό ν™•μΈν•˜λŠ”λ° λ§Œμ•½ fetchν•˜λŠ” 데이터가 μ—¬λŸ¬ μ’…λ₯˜, λ§Žλ‹€λ©΄ 상황에 λ”°λ₯Έ μ˜ˆμ™Έμ²˜λ¦¬λ₯Ό ν•΄μ€˜μ•Ό ν•œλ‹€. μœ„ μ½”λ“œλŠ” isLoadedλΌλŠ” μƒνƒœλ₯Ό 톡해 ν•˜λ‚˜μ˜ μ˜ˆμ™Έμ²˜λ¦¬λ₯Ό ν–ˆμ§€λ§Œ 경우의 μˆ˜κ°€ λ§Žμ•„μ§€κ²Œ 되면 μ‚Όν•­μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•  경우 였히렀 가독성이 μ•ˆ μ’‹μ•„μ§ˆκ²ƒμ΄κ³  if 문을 ν†΅ν•œ λ§Žμ€ λΆ„κΈ°μ²˜λ¦¬κ°€ ν•„μš”ν•˜κ²Œ 될 것이닀. 반면 Suspense의 경우 데이터λ₯Ό λ‘œλ“œν•˜κ³  κ·Έ 결과에 따라 λŒ€μ²΄ λ…Έλ“œ λ˜λŠ” μ»΄ν¬λ„ŒνŠΈλ₯Ό λžœλ”λ§ ν•΄μ£ΌκΈ° λ•Œλ¬Έμ— μ˜ˆμ™Έμ²˜λ¦¬κ°€ νŽΈν•˜λ‹€. κ²°κ΅­ μ„œμŠ€νŽœμŠ€λ₯Ό μ΄μš©ν•˜λ©΄ μ½”λ“œ 가독성과 μœ μ§€ λ³΄μˆ˜μ„±μ„ ν–₯μƒμ‹œν‚¬ 수 μžˆμ„ κ²ƒμœΌλ‘œ κΈ°λŒ€λœλ‹€.

 

κ΅¬ν˜„λœ κ²°κ³Όλ¬Ό!!

 

λΆ€μ‘±ν•œ 뢀뢄을 μ±„μš°κ³  React.lazy()에 λŒ€ν•΄μ„œλ„ μΆ”κ°€ν•  μ˜ˆμ •μž…λ‹ˆλ‹€! ν˜Ήμ‹œ ν‹€λ¦° λ‚΄μš©μ΄ μžˆλ‹€λ©΄ λŒ“κΈ€λ‘œ λ‚¨κ²¨μ£Όμ‹œλ©΄ κ°μ‚¬ν•˜κ² μŠ΅λ‹ˆλ‹€!!!

'React' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

[React] 라이프 사이클(Life Cycle )  (0) 2023.03.27
[React] μ „μ—­ μƒνƒœ 관리 맛보기  (0) 2023.03.02
[React] setState callBack ν•¨μˆ˜  (0) 2022.12.23
[React] Lazy initialization  (0) 2022.12.22