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

Frontend

React 18 ๋ฒ„์ „ ์—…๋ฐ์ดํŠธ ์ •๋ฆฌ

1. Automatic Batching

  • ์ž๋™ ๋ฐฐ์น˜๋ž€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ state ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด render ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ(๋ฆฌ๋ Œ๋”๋ง ์„ฑ๋Šฅ ๊ฐœ์„ )ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ์กด 17 ๋ฒ„์ „์—์„œ๋„ ์ด๋Ÿฌํ•œ ๋ฐฐ์นญ ์ฒ˜๋ฆฌ๋Š” ๋˜์—ˆ์ง€๋งŒ ๋น„๋™๊ธฐ ๋ถ€๋ถ„์—์„œ๋Š” ์ž๋™ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๊ฐ€ ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ 18 ๋ฒ„์ „๋ถ€ํ„ฐ๋Š” ๋น„๋™๊ธฐ์—์„œ๋„ ์ž๋™ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
import { useState } from "react";
import "./App.css";

function App() {
  const [number, setNumber] = useState(0);
  const [boolean, setBoolean] = useState(true);

  const onClick = () => {
    setNumber((prev) => prev + 1);
    setBoolean(!boolean);
  };

  console.log("๋ Œ๋”๋ง");

  return (
    <div className="App">
      <h1>{number}</h1>
      <h2>{boolean.toString()}</h2>
      <button onClick={onClick}>๋ฒ„ํŠผ</button>
    </div>
  );
}

export default App;

์œ„ ์†Œ์Šค๋Š” ์•„๋ž˜ ํ™”๋ฉด์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

์ฒซ ๋ Œ๋”๋ง

์œ„ ํ™”๋ฉด์€ ์ดˆ๊ธฐ์— ํ™”๋ฉด์ด ์ถœ๋ ฅ๋  ๋•Œ ๋ Œ๋”๋ง์ด ์ฝ˜์†”์— ์ฐํž™๋‹ˆ๋‹ค.

๋ฒ„ํŠผ ํด๋ฆญ ํ›„ ๋ Œ๋”๋ง

์œ„์™€ ๊ฐ™์ด ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด onClickํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  setNumber์™€ setBoolean์˜ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด ๋ฆฌ๋ Œ๋”๋ง์„ ํ•œ ๋ฒˆ๋งŒ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€๋Š” ๋ฒ„์ „ 17๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

๋น„๋™๊ธฐ์—์„œ ์ž๋™๋ฐฐ์น˜

  • ๊ธฐ์กด 17 ๋ฒ„์ „์€ ๋น„๋™๊ธฐ์—์„œ ์ž๋™ ๋ฐฐ์น˜๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์•˜์ง€๋งŒ 18 ๋ฒ„์ „์—์„œ๋Š” ๋น„๋™๊ธฐ์—์„œ๋„ ์ž๋™ ๋ฐฐ์น˜๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” ๋น„๋™๊ธฐ์—์„œ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ์†Œ์Šค์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

import { useState } from "react";
import "./App.css";

function App() {
  const [number, setNumber] = useState(0);
  const [boolean, setBoolean] = useState(true);

  const onClick = () => {
    setTimeout(() => {
      setNumber((prev) => prev + 1);
      setBoolean(!boolean);
    }, 2000);
  };

  console.log("๋ Œ๋”๋ง");

  return (
    <div className="App">
      <h1>{number}</h1>
      <h2>{boolean.toString()}</h2>
      <button onClick={onClick}>๋ฒ„ํŠผ</button>
    </div>
  );
}

export default App;

์•„๋ž˜ ํ™”๋ฉด๊ณผ ๊ฐ™์ด 18 ๋ฒ„์ „์€ ๋น„๋™๊ธฐ์—์„œ๋„ ์ž๋™ ๋ฐฐ์น˜๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

 

 

 

์•„๋ž˜์™€ ๊ฐ™์ด ํ•˜๋‚˜๋Š” ๋™๊ธฐ์  ์ƒํƒœ ์—…๋ฐ์ดํŠธ์™€ ๋น„๋™๊ธฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๊ฒฝ์šฐ๋Š” ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

import { useState } from "react";
import "./App.css";

function App() {
  const [number, setNumber] = useState(0);
  const [boolean, setBoolean] = useState(true);

  const onClick = () => {
    setTimeout(() => {
      setNumber((prev) => prev + 1);
    }, 2000);
    setBoolean(!boolean);
  };

  console.log("๋ Œ๋”๋ง");

  return (
    <div className="App">
      <h1>{number}</h1>
      <h2>{boolean.toString()}</h2>
      <button onClick={onClick}>๋ฒ„ํŠผ</button>
    </div>
  );
}

export default App;

์•„๋ž˜์™€ ๊ฐ™์ด ๋น„๋™๊ธฐ์—์„œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ์™€ ์ผ๋ฐ˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ๋”ฐ๋กœ ๋ฆฌ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.

setTimeout ์•ˆ์—์„œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋‚˜๋กœ ๊ทธ๋ฃนํ™”ํ•˜๊ณ  onClick ํ•จ์ˆ˜์˜ setTimeout ๋ฐ–์—์„œ์˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ๊ทธ๋ฃนํ™”ํ•ฉ๋‹ˆ๋‹ค.

 

๋งŒ์•ฝ ์ž๋™ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋ฅผ ์›ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด?

  • react-dom์˜ flushSync์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด flushSync๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ์ž๋™ ๋ฐฐ์นญ ์ฒ˜๋ฆฌ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

import { useState } from "react";
import { flushSync } from "react-dom";
import "./App.css";

function App() {
  const [number, setNumber] = useState(0);
  const [boolean, setBoolean] = useState(true);

  const onClick = () => {
    flushSync(() => {
      setNumber((prev) => prev + 1);
    });

    flushSync(() => {
      setBoolean(!boolean);
    });
  };

  console.log("๋ Œ๋”๋ง");

  return (
    <div className="App">
      <h1>{number}</h1>
      <h2>{boolean.toString()}</h2>
      <button onClick={onClick}>๋ฒ„ํŠผ</button>
    </div>
  );
}

export default App;

์•„๋ž˜ ํ™”๋ฉด๊ณผ ๊ฐ™์ด ์ž๋™ ๋ฐฐ์น˜๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š์€ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ž๋™๋ฐฐ์น˜๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š์Œ

 

2. useTranstion

  • ๊ฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ์— ๋Œ€ํ•œ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” hook์ž…๋‹ˆ๋‹ค.
  • isPending: boolean ์€ state๋ณ€๊ฒฝ ์งํ›„์—๋„ ๋ฆฌ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š๊ณ  UI๋ฅผ ์ž ์‹œ ์œ ์ง€ํ•˜๋Š” ์ƒํƒœ์ž…๋‹ˆ๋‹ค.
  • startTransition์€ ํด๋ฆญ์ด๋‚˜ ํ‚ค ์ž…๋ ฅ์— ์˜ํ•ด ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ๋‚ด๋ถ€์— ์„ ์–ธํ•œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋Š” ์ค‘๋‹จ๋˜๊ณ  ํด๋ฆญ์ด๋‚˜ ํ‚ค ์ž…๋ ฅ์ด ๋๋‚œ ํ›„ ์ดํ›„์— ํ•ด๋‹น ์ƒํƒœ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. 

์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?

ํ•œ๋ฒˆ ๋ Œ๋”๋ง ์—ฐ์‚ฐ์ด ์‹œ์ž‘๋˜๋ฉด ๋ฉˆ์ถœ ์ˆ˜๊ฐ€ ์—†๋Š” ๋ธ”๋กœํ‚น ๋ Œ๋”๋ง ๋ฌธ์ œ๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์ฐฝ์— ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•  ๋•Œ ์ž…๋ ฅ๊ณผ ํ•จ๊ป˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์„ ํ•˜๊ณ  ์žˆ์Œ์—๋„ ๋ Œ๋”๋ง ์—ฐ์‚ฐ์ด ์‹œ์ž‘๋˜๋ฉด ๋ฉˆ์ถœ ์ˆ˜ ์—†์–ด ์ž…๋ ฅ์ฐฝ์ด ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋Š” ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ์ข‹์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ์™€ ์ „ํ™˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ „ํ™˜ ์—…๋ฐ์ดํŠธ ๋•Œ๋ฌธ์— ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐฉํ•ด๋˜์–ด ๋ธ”๋กœํ‚น ๋ Œ๋”๋ง ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

  • ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ: ์ž…๋ ฅ, ํด๋ฆญ, ๋ˆ„๋ฅด๊ธฐ์™€ ๊ฐ™์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘์ ์ธ ์ƒํ˜ธ ์ž‘์šฉ
  • ์ „ํ™˜ ์—…๋ฐ์ดํŠธ: UI ์ „ํ™˜

์ด์ „ ๋ฒ„์ „์—์„œ๋Š” ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋“ฑ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ Debounce, Throttle ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ์ • ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค. 

Debounce์™€ Throttle์˜ ๊ฒฝ์šฐ ์ผ์ • ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์œผ๋กœ ๋ฌธ์ œ๋ฅผ ์ž ์‹œ ๋ฏธ๋ฃจ๋Š” ๋ฐฉ์‹์ด์—ˆ๋‹ค๋ฉด startTranstion์„ ์‚ฌ์šฉํ•ด์„œ ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋‚ฎ์ถ”๊ณ  ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋†’์—ฌ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰ ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ๋ฅผ ์ „ํ™˜ ์—…๋ฐ์ดํŠธ๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋†’๊ฒŒ ์„ค์ •ํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Debounce

  • ์ž…๋ ฅ์ด ๋‹ค ๋๋‚˜๋ฉด ์ผ์ • ์‹œ๊ฐ„ ๋’ค ํ™”๋ฉด ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ž…๋ ฅํ•˜๋Š” ๋™์•ˆ ํ™”๋ฉด์„ ๊ทธ๋ฆฌ์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. 

throttle 

  • ์ผ์ •ํ•œ ์ฃผ๊ธฐ๋กœ ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๊ฒŒ ํ•œ๋‹ค
  • ์‚ฌ์šฉ์ž๊ฐ€ ์ผ์ • ์ฃผ๊ธฐ ๋™์•ˆ ์ž…๋ ฅ์„ ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ์—๋„ ๊ธฐ๋‹ค๋ ค์•ผ ํ•œ๋‹ค.

startTransition

  • ํ™”๋ฉด ์—…๋ฐ์ดํŠธ ์ค‘์—๋„ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ Œ๋”๋ง ํ•˜๋Š” ์™€์ค‘์—๋„ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์ผ์ด ์ƒ๊ธฐ๋ฉด ๊ทธ๊ฒƒ์„ ๋จผ์ € ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • timeountMS ์„ค์ •ํ•˜๋ฉด ํ•ด๋‹น ์‹œ๊ฐ„ ๋™์•ˆ ๋ Œ๋”๋ง์„ ๊ธฐ๋‹ค๋‹ค๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ–์ง€ ๋ชปํ•˜๋ฉด ๊ฐ•์ œ๋กœ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” useTrasition์„ ๊ตฌํ˜„ํ•œ ์†Œ์Šค์ž…๋‹ˆ๋‹ค.

import { useState, useTransition } from "react";

import "./App.css";

function App() {
  const [isPending, startTransition] = useTransition({ timeoutMs: 5000 });

  const [keyword, setKeyword] = useState("");
  const [list, setList] = useState([]);

  const onChange = (e) => {
    setKeyword(e.target.value); // ๊ธด๊ธ‰ ์—…๋ฐ์ดํŠธ

    startTransition(() => {
      // ์ „ํ™˜ ์—…๋ฐ์ดํŠธ
      setList([...Array(e.target.value.length * 1000)]);
    });
  };

  return (
    <div className="App">
      <input type={"text"} value={keyword} onChange={onChange} />
      <div>
        {isPending && <p>...isPending</p>}
        {list.map((el, i) => {
          return (
            <div
              key={i}
              style={{
                width: 100,
                height: 100,
                margin: 3,
                backgroundColor: "black",
              }}
            ></div>
          );
        })}
      </div>
    </div>
  );
}

export default App;

3. Suspense and SSR

์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์˜ ๊ฒฝ์šฐ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋ชจ๋‘ ์ฑ„์›Œ์ง„ html ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ›์•„ ๋ Œ๋”๋งํ•˜๊ณ  ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๋ฅผ ๋กœ๋”ฉ ํ›„ Hydration ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋กœ๋”ฉํ•˜๊ธฐ ์ „์—๋Š” Hydration(์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—ฐ๊ฒฐ) ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐˆ ์ˆ˜ ์—†๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ๊ณผ ์ƒํ˜ธ ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Hydration ๋‹จ๊ณ„๊นŒ์ง€ ์™„๋ฃŒ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 

 

์ด์™€ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ Suspense์™€ lazy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ Œ๋”๋ง ์„ฑ๋Šฅ์„ ํ–ฅ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

lazy & Suspense

๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ fetch ๋˜์–ด์•ผ ๋ Œ๋” ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ Suspense์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ fallback์œผ๋กœ ์•„์ง ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์•˜์„ ๋•Œ์˜ ์ปดํฌ๋„ŒํŠธ(์˜ˆ: loading spinner)๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ fetch ๋œ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ ๋ถ€๋ถ„๋ถ€ํ„ฐ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. lazy & Suspense๋ฅผ ํ™œ์šฉํ•ด ๊ตฌํ˜„ํ•  ๊ฒฝ์šฐ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„์ง ๋ Œ๋”๋ง ๋˜์ง€ ์•Š์•˜์–ด๋„ ์ƒ๊ด€์—†์ด ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋“ค์€ hydration์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}