๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Frontend/React.js

React ์„ฑ๋Šฅ ๊ฐœ์„ ํ•˜๊ธฐ

๐Ÿ“’ ์™œ?

โ—ผ React๋Š” SPA๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ ์ฒ˜์Œ ๋ฉ”์ธ ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋ฉด, ๋ชจ๋“  js ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œ ํ•˜์—ฌ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์†๋„๊ฐ€ ๋Š๋ ค, ์„ฑ๋Šฅ ๊ฐœ์„  ์ž‘์—…์„ ์œ„ํ•œ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๊ณ ์ž ํ•œ๋‹ค.


๐Ÿ“’ React ์„ฑ๋Šฅ ๊ฐœ์„ ํ•˜๊ธฐ

โœ” ๊ฐœ๋ฐœ ๋„๊ตฌ ์„ค์น˜

โ—ผ React Developer Tools๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

 

โ—ผ Components ์ฐฝ์—์„œ Component์˜ ๊ตฌ์กฐ, Props ๋“ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

โ—ผ Profiler ์ฐฝ์—์„œ ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ๋Š” Component๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค. (๋…นํ™” ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ํŽ˜์ด์ง€ ์ด๋™ ๋ฐ ๋ฒ„ํŠผ ํด๋ฆญ ๋“ฑ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋ฉด Component ๋ณ„๋กœ ๋ Œ๋”๋ง ์†๋„๋ฅผ ์•Œ๋ ค์ค€๋‹ค.)

๐Ÿ’ฅ ๋Œ€๋ถ€๋ถ„์˜ ์ง€์—ฐ์€ ์„œ๋ฒ„์˜ ํ†ต์‹ ์ด ๋Š๋ฆฐ ๊ฒƒ

 

โœ” Lazy ๋กœ๋”ฉ

โ—ผ React๋Š” SPA์ด๊ธฐ ๋•Œ๋ฌธ์—, ํ•˜๋‚˜์˜ html, js, css ํŒŒ์ผ์— ๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด ์žˆ๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ๋ฉ”์ธ ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋ฉด ํฐ js ํŒŒ์ผ์„ ๋‹ค์šด ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์—, ์ฒ˜์Œ ๋ Œ๋”๋ง ์†๋„๊ฐ€ ๋Š๋ ค์ง„๋‹ค.

โ—ผ ๋”ฐ๋ผ์„œ, ๋ฉ”์ธ ํŽ˜์ด์ง€์— ์ ‘์†ํ•  ๋•Œ, ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” Component๋“ค์„ ๋จผ์ € ๋กœ๋“œ ์‹œํ‚ค์ง€ ์•Š๋„๋ก Lazy ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

import Detail from "pages/items/item"; // ์‚ฌ์šฉ X
 
const Detail = lazy(() => import("pages/items/detail"));

โ—ผ ์œ„์™€ ๊ฐ™์ด import ๋ฌธ์€ ์ œ๊ฑฐํ•˜๊ณ , lazy()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Detail์„ Lazy ๋กœ๋”ฉํ•  ์ˆ˜ ์žˆ๋‹ค.

โ—ผ Lazy ๋กœ๋”ฉ์„ ํ•˜๋ฉด, ํ•ด๋‹น Component๋Š” ๋นŒ๋“œ ์‹œ, ๋ณ„๋„์˜ js ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌ๋œ๋‹ค.

โ—ผ ์‚ฌ์šฉ์ž๊ฐ€ Detail Component๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ๋•Œ, ๋กœ๋”ฉ์ด ์ด๋ฃจ์–ด์ง€๋ฏ€๋กœ ์ง€์—ฐ ์‹œ๊ฐ„์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

import React, { Suspense, lazy } from "react";

const Detail = lazy(() => import("pages/items/detail"));

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
    <Route
      path="/detail/:id"
      element={
        <Suspense fallback={<div>๋กœ๋”ฉ ์ค‘...</div>}>
          <Detail />
        </Suspense>
      }
    />
);

โ—ผ Suspend Component๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ง€์—ฐ ์‹œ๊ฐ„ ๋™์•ˆ ๋‹ค๋ฅธ Element๋ฅผ ํ™”๋ฉด์— ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

โ—ผ Lazy ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์ข‹๋‹ค.

 

import React, { Suspense, lazy } from "react";

const Detail = lazy(() => import("pages/items/detail"));
const Cart = lazy(() => import("pages/cart/cart"));

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <Suspense fallback={<div>๋กœ๋”ฉ ์ค‘...</div>}>
      <Routes>
        <Route path="/detail/:id" element={<Detail />} />
        <Route path="/cart" element={<Cart />}></Route>
      </Routes>
    </Suspense>
  </BrowserRouter>
);

โ—ผ Routes Component ์•ˆ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ Lazy Component๊ฐ€ ์žˆ์œผ๋ฉด, ์œ„์™€ ๊ฐ™์ด Routes Component ์œ„์— Suspend Component๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

โœ” ์ž์‹ Component ์žฌ๋ Œ๋”๋ง ๋ง‰๊ธฐ

โ—ผ React์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ถ€๋ชจ Component๊ฐ€ ์žฌ๋ Œ๋”๋ง๋˜๋ฉด, ์ž์‹ Component๋„ ์žฌ๋ Œ๋”๋ง๋œ๋‹ค.

โ—ผ ๋ถ€๋ชจ Component๋งŒ ์žฌ๋ Œ๋”๋งํ•˜๋ฉด ๋˜๋Š” ์ƒํ™ฉ์— ์‚ฌ์šฉํ•˜์—ฌ, ์ž์‹ Component ์žฌ๋ Œ๋”๋ง ๋น„์šฉ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.

 

import { memo } from "react";

const CartBody = memo(({ item }) => {
  return (
    <tr>
      <td>{item.id}</td>
      <td>{item.title}</td>
      <td>{"count" in item ? item.count : 0}</td>
      <td>{item.price}</td>
    </tr>
  );
});

export default CartBody;

โ—ผ Component๋ฅผ ์ƒ์„ฑํ•  ๋•Œ, memo๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ์‹ธ๋ฉด, ์ž์‹ Component๋Š” ์žฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋Š”๋‹ค.

   ๐Ÿ’ฅ ์ž์‹ Component์—์„œ ์‚ฌ์šฉํ•˜๋Š” Props์˜ ๊ฐ’์ด ๋ณ€ํ•˜๋ฉด ์žฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•œ๋‹ค.

โ—ผ memo๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ์กด props๋ž‘ ์‹ ๊ทœ props๋ž‘ ๋น„๊ต ์—ฐ์‚ฐ์ด ์ด๋ฃจ์–ด์ง€๊ณ , ๋‹ค๋ฅด๋ฉด ์žฌ๋ Œ๋”๋ง์„ ์ง„ํ–‰ํ•œ๋‹ค.

โ—ผ Props ๋น„๊ต ์—ฐ์‚ฐ์ด ์ด๋ฃจ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์—, ์žฌ๋ Œ๋”๋ง์ด ๋ฌด๊ฑฐ์šด Component์—๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

 

โœ” ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์˜ ํ•จ์ˆ˜ ์‹คํ–‰์„ Component Mount ์‹œ์—๋งŒ ๋™์ž‘

import { useMemo } from "react";

const heavy = () => {
  for (let i = 0; i < 1000000; i++) {
    console.log(i);
  }
};

const CartBody = memo(({ item }) => {
  const result = useMemo(() => {
    heavy();
  });
  
  return (
    <tr>
      <td>{item.id}</td>
      <td>{item.title}</td>
      <td>{"count" in item ? item.count : 0}</td>
      <td>{item.price}</td>
    </tr>
  );
});

export default CartBody;

โ—ผ ํ•จ์ˆ˜์˜ ์‹คํ–‰์„ Component์˜ mount ์‹œ์ ์—๋งŒ ๋™์ž‘ํ•˜๋„๋ก ๋ณด์žฅํ•ด์ค€๋‹ค.

โ—ผ ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ๋’ค์— ๋ณ€ํ™” ๊ฐ์ง€ state Array๋ฅผ ์„ค์ •ํ•ด์„œ ํ•ด๋‹น state๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

โ—ผ useEffect์™€ ์‚ฌ์šฉ๋ฒ•์ด ๋™์ผํ•˜๋‹ค.

   ๐Ÿ’ฌ useEffect๋Š” return()์œผ๋กœ ๊ธฐ์ž…ํ•œ html ์ฝ”๋“œ๊ฐ€ ๋ Œ๋”๋ง ๋œ ํ›„, ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋ฐ˜๋ฉด, useMemo๋Š” html ์ฝ”๋“œ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ, ๋™์‹œ์— ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

 

โœ” Input ๊ฐ’ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•œ State ์‹œ ์„ฑ๋Šฅ ์ €ํ•˜ ๋ฐœ์ƒ ํ•ด๊ฒฐ

const Test = () => {
  let [inputVal, setInputVal] = useState("");
  const heavy = new Array(100000).fill(0);
  
  return (
    <div>
      <input
        type="text"
        onChange={(e) => {
          startTransition(() => {
            setInputVal(e.target.value);
          });
        }}
      ></input>
      {heavy.map(() => (
        <div>{inputVal}</div>
      ))}
    </div>
  );
}

โ—ผ input ์ž…๋ ฅ ๊ฐ’์ด ๋ณ€ํ•  ๋•Œ๋งˆ๋‹ค, ์žฌ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚˜ heavy๋ผ๋Š” ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์ด ๊ณ„์† ์‹คํ–‰๋œ๋‹ค. 

 

import { useState, useTransition } from "react";

const Test = () => {
  let [inputVal, setInputVal] = useState("");
  let [isPending, startTransition] = useTransition();
  const heavy = new Array(100000).fill(0);
  
  return (
    <div>
      <input
        type="text"
        onChange={(e) => {
          startTransition(() => {
            setInputVal(e.target.value);
          });
        }}
      ></input>
      {heavy.map(() => (
        <div>{inputVal}</div>
      ))}
    </div>
  );
}

โ—ผ useTransition์„ ์‚ฌ์šฉํ•ด์„œ state ๋ณ€ํ™”๋กœ ์ธํ•œ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์ค„์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.

โ—ผ startTransition ํ•จ์ˆ˜๋กœ state ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋ถ€๋ถ„์„ ๊ฐ์‹ธ์ค€๋‹ค.

โ—ผ ๋ธŒ๋ผ์šฐ์ €๋Š” ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋กœ ๋™์‹œ์— ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋‹ค. ๊ทธ๋Ÿฐ๋ฐ input ๊ฐ’์˜ ๋ณ€๊ฒฝ์„ ๋ณด์—ฌ์ฃผ๊ณ , input ๊ฐ’์˜ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•ด ์žฌ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚˜ <div> ํƒœ๊ทธ๋ฅผ ๋ณ€๊ฒฝ๋œ state๋ฅผ ํ™œ์šฉํ•ด, ๊ณ„์† ์ƒ์„ฑํ•˜๋ ค๋Š” ์ž‘์—…์ด ์ถฉ๋Œํ•˜๊ฒŒ ๋œ๋‹ค.

โ—ผ startTransition์œผ๋กœ ๊ฐ์‹ธ์ง„ ์ฝ”๋“œ๋Š” ๋Šฆ๊ฒŒ ์‹คํ–‰์‹œ์ผœ ์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ๋™์‹œ์— ํ•˜๋ ค๋Š” ์ž‘์—…์œผ๋กœ ๋Š๋ ค์ง€๋Š” ํ˜„์ƒ์„ ํ’€์–ด์ค€๋‹ค.

โ—ผ isPending์€ startTransition์œผ๋กœ ๊ฐ์‹ผ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰ ์ค‘์ด๋ฉด true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

import { useState, useDeferredValue } from "react";

const Test = () => {
  let [inputVal, setInputVal] = useState("");
  let state = useDeferredValue(inputVal);
  const heavy = new Array(100000).fill(0);
  
  return (
    <div>
      <input
        type="text"
        onChange={(e) => {
          setInputVal(e.target.value);
        });
      ></input>
      {heavy.map(() => (
        <div>{state}</div>
      ))}
    </div>
  );
}

โ—ผ ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•˜๋Š” ์ฝ”๋“œ๋กœ useDeferredValue๋กœ ์„ค์ •ํ•œ state๋กœ ํ•˜๋ ค๋Š” ์ž‘์—…์ด ๊ฐ€์žฅ ๋Šฆ๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋„๋ก ํ•œ๋‹ค.

'Frontend > React.js' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

CSR๊ณผ SSR, SSG  (0) 2023.05.07
React ์‚ฌ์šฉํ•˜๊ธฐ  (0) 2023.05.01