๋ฐ˜์‘ํ˜•

React.lazy()


React์—์„œ ์ฝ”๋“œ ๋ถ„ํ• ์„ ๋ชฉ์ ์œผ๋กœ(Chunk ๋ถ„๋ฆฌ) ์ปดํฌ๋„ŒํŠธ๋ฅผ Dynamic Import ํ•  ๋•Œ React.lazy() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ•œ ์‹œ์ ์—๋งŒ ๋กœ๋“œ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋กœ๋”ฉ ์ค‘ ํ‘œ์‹œํ•  ์ปดํฌ๋„ŒํŠธ๋‚˜ ๋ฉ”์‹œ์ง€๋Š” <Suspense>๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์„ค์ •ํ•œ๋‹ค. ์—ฌ๋Ÿฌ ๊ฐœ์˜ lazy ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฌถ์–ด์„œ fallback์„ ํ•œ ๋ฒˆ์— ํ‘œ์‹œํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

๋˜ํ•œ, React.lazy()๋กœ ๋ถˆ๋Ÿฌ์˜ค๋ ค๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ default export๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋‚ด๋ณด๋‚ด์•ผ ํ•œ๋‹ค.

// Child.tsx
export default function Child() { /* ... */ }
// Parent.tsx
import { Suspense } from 'react';

// ์ปดํฌ๋„ŒํŠธ ๋ฐ”๋””์—์„œ ํ˜ธ์ถœํ•˜๋ฉด ์ •์ƒ ์ž‘๋™ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ฃผ์˜
const Child = React.lazy(() => import('./Child'));

const Parent = () => {
  return (
    <Suspense fallback={<Loading />}>
      <Child />
    </Suspense>
  );
}

 

์ปดํฌ๋„ŒํŠธ๊ฐ€ named export ๋ผ๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ๋ชจ๋“ˆ์˜ ํ”„๋กœํผํ‹ฐ(์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„)๋ฅผ default ์†์„ฑ์— ๋‹ด์•„์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” Workaround(์ฐจ์„ ์ฑ…)๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

const Child = React.lazy(() =>
  import('./Child').then((module) => ({ default: module.Child })),
);

 

Proxy๋ฅผ ํ™œ์šฉํ•œ ์ปค์Šคํ…€ LazyImport


๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ์˜ ์ฝ๊ธฐ / ์“ฐ๊ธฐ ์ž‘์—…์„ ์ค‘๊ฐ„์— ๊ฐ€๋กœ์ฑ„์„œ ์›ํ•˜๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ Proxy๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. Proxy๋กœ React.lazy() ํ•จ์ˆ˜๋ฅผ ๋žฉํ•‘ํ•ด์„œ ์ปค์Šคํ…€ํ•˜๋ฉด named export ์ปดํฌ๋„ŒํŠธ๋„ ๊น”๋”ํ•˜๊ฒŒ ๋™์  importํ•  ์ˆ˜ ์žˆ๋‹ค.

import { ComponentType, lazy } from 'react';

type ComponentName = string;
type Loader<T> = () => Promise<T>;
type NamedComponents = Record<ComponentName, unknown>;

export const lazyImport = <T extends NamedComponents>(loader: Loader<T>) => {
  return new Proxy({} as T, {
    get: (_target, name: ComponentName) => {
      return lazy(async () => {
        const module = await loader();
        const Component = module[name] as ComponentType<T>;

        return { default: Component };
      });
    },
  });
};
// lazyImport ์‚ฌ์šฉ
const { Home, About } = lazyImport(() => import('@/features/misc'));

 

lazyImport๋Š” Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” loader ํ•จ์ˆ˜(() => import('path'))๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ Proxy ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด ๋ฐ˜ํ™˜๋œ Proxy ๊ฐ์ฒด๋Š” GET ํŠธ๋žฉ์„ ํ†ตํ•ด ์„ธ ๋‹จ๊ณ„์˜ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•๋œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

 

  1. ๋ชจ๋“ˆ์˜ ํ”„๋กœํผํ‹ฐ(์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„)์— ์ ‘๊ทผํ•  ๋•Œ GET ํŠธ๋žฉ ์‹คํ–‰
  2. loader ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ชจ๋“ˆ ๋กœ๋“œ(import)
  3. ๋ถˆ๋Ÿฌ์˜จ ๋ชจ๋“ˆ์˜ ํ”„๋กœํผํ‹ฐ(์ปดํฌ๋„ŒํŠธ) ๋ฐ˜ํ™˜

 

์˜ˆ๋ฅผ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์ด ๋‘ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚ด๋ณด๋‚ด๊ณ  ์žˆ๋Š” misc.tsx ํŒŒ์ผ์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ์ด ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ค๋ฉด ๋ชจ๋“ˆ์€ {Home: (props) => {...}, About: (props) => {...}} ํ˜•ํƒœ๊ฐ€ ๋œ๋‹ค.

// src/features/misc.tsx
export const Home = (props) => { /* ... */ };
export const About = (props) => { /* ... */ };

 

const { Home } = lazyImport(...) ์ด๋Ÿฐ ์‹์œผ๋กœ Home ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹นํ•˜๋ฉด GET ํŠธ๋žฉ์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž์ธ name ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์€ 'Home'์ด ๋œ๋‹ค. ์ด์–ด์„œ loader ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด misc.tsx ๋ชจ๋“ˆ์„ ๋ถˆ๋Ÿฌ์˜จ ํ›„ module['Home'] ๋ฐ˜ํ™˜ ๊ฐ’์ธ Home ์ปดํฌ๋„ŒํŠธ๋ฅผ default ์†์„ฑ์— ๋‹ด์•„์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

๋ ˆํผ๋Ÿฐ์Šค


 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•