๐Ÿ“Ž React

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

Learn React - Adding Interactivity

๋ฆฌ์•กํŠธ์—์„œ๋Š” ์‹œ๊ฐ„์ด ์ง€๋‚จ์— ๋”ฐ๋ผ ๋ณ€ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ state๋ผ๊ณ  ํ•œ๋‹ค.

Responding to events

React๋ฅผ ํ†ตํ•ด JSX์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ํด๋ฆญ, ๋งˆ์šฐ์Šค์˜ค, input์— ์ดˆ์  ๋งž์ถ”๊ธฐ ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์— ์˜ํ•ด ์‹คํ–‰๋˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

State: a componentโ€™s memory

์ปดํฌ๋„ŒํŠธ๋Š” ์ƒํ˜ธ ์ž‘์šฉ์˜ ๊ฒฐ๊ณผ๋กœ ํ™”๋ฉด์˜ ๋‚ด์šฉ์„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. ์ด ๋•Œ, ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋Ÿฐ ๋ณ€ํ•˜๋Š” ๋‚ด์šฉ์„ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค. React์—์„œ๋Š” ์ด๋Ÿฐ ์ข…๋ฅ˜์˜ ์ปดํฌ๋„ŒํŠธ๋ณ„ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ state๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

์ปดํฌ๋„ŒํŠธ์— state๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด useState ํ›…์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ํ›…๋“ค์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ React ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํŠน์ˆ˜ํ•œ ํ•จ์ˆ˜๋“ค์ด๋‹ค. useState ํ›…์„ ์‚ฌ์šฉํ•˜๋ฉด state ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ดˆ๊ธฐ state๋ฅผ ๋ฐ›์•„ ํ˜„์žฌ state์™€ ์ด๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋Š” state ์„ค์ •์ž ํ•จ์ˆ˜์˜ ๊ฐ’ ์Œ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Render and commit

์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๊ธฐ ์ „์—, ์ปดํฌ๋„ŒํŠธ๋“ค์€ ๋ฆฌ์•กํŠธ์—์„œ ๋ Œ๋”๋ง๋˜์–ด์•ผ ํ•œ๋‹ค. ์•„๋ž˜์˜ ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค.

  • ๋ Œ๋”๋ง ๋ฐœ๋™
  • ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง
  • DOM์— ์ปค๋ฐ‹

State as a snapshot

์ผ๋ฐ˜ JavaScript ๋ณ€์ˆ˜์™€ ๋‹ฌ๋ฆฌ React state๋Š” ์Šค๋ƒ…์ƒท์ฒ˜๋Ÿผ ๋™์ž‘ํ•œ๋‹ค. state ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ด๋„ ์ด๋ฏธ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” state ๋ณ€์ˆ˜๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๊ณ  ๋Œ€์‹  ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค.

console.log(count);  // 0
setCount(count + 1); // Request a re-render with 1
console.log(count);  // Still 0!

Queueing a series of state updates

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}

// '+3' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด๋„ ์ˆซ์ž๋Š” 1์”ฉ๋งŒ ์ฆ๊ฐ€ํ•œ๋‹ค.

setState๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ƒˆ๋กœ์šด ๋ Œ๋”๋ง์„ ์š”์ฒญํ•˜์ง€๋งŒ ์ด๋ฏธ ์‹คํ–‰ ์ค‘์ธ ์ฝ”๋“œ์—์„œ๋Š” ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค. ์œ„ ๋กœ์ง์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•˜๋ฉด ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(s => s + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}

Updating objects in state

state์—๋Š” ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•œ JS์—์„œ์˜ ๋ชจ๋“  ๊ฐ’์„ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค. state์— ๊ฐ์ฒด๋ฅผ ํ• ๋‹นํ–ˆ์„ ๋•Œ๋Š”, ํ•ด๋‹น ๊ฐ์ฒด ๋‚ด๋ถ€ ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค. ๋Œ€์‹  ๊ฐ์ฒด๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋ฉด ์ƒˆ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ธฐ์กด ๊ฐ์ฒด์˜ ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“  ๋‹ค์Œ ํ•ด๋‹น ๋ณต์‚ฌ๋ณธ์„ ์‚ฌ์šฉํ•˜๋„๋ก state๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•œ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ...์™€ ๊ฐ™์€ ์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด์„ ๋ณต์‚ฌํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.
(์ถ”๊ฐ€) ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ๊ฐ์ฒด๋ฅผ ๋ณต์‚ฌํ•˜๋Š” ๊ฒƒ์ด ์‹ซ๋‹ค๋ฉด Immer์™€ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ์˜ ์–‘์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

Updating arrays in state

๊ฐ์ฒด์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ฐฐ์—ด ๋˜ํ•œ state์— ํ• ๋‹นํ•œ ํ›„, ๊ทธ state(๋ฐฐ์—ด์ด ํ• ๋‹น๋œ)์˜ ๋‚ด๋ถ€๋ฅผ ์ง์ ‘ ๋ณ€๊ฒฝํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค. ์ƒˆ๋กœ์šด ๋ฐฐ์—ด๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ ๋ฐฐ์—ด ํ˜น์€ ๊ธฐ์กด ๋ฐฐ์—ด์˜ ๋ณต์‚ฌ๋ณธ์„ ์ƒ์„ฑํ•œ ํ›„ setState๋กœ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.


Responding to Events

Adding event handlers

๋ฆฌ์•กํŠธ๋Š” jsx ๋‚ด๋ถ€์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ์ •์˜๋œ๋‹ค.
  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ handleXXX์™€ ๊ฐ™์€ ์ด๋ฆ„์„ ๊ฐ€์ง„๋‹ค.
export default function Button() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

์ฃผ์˜! ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์€ ํ˜ธ์ถœ๋œ ํ•จ์ˆ˜(๋ฆฌํ„ด๊ฐ’)์ด ์•„๋‹Œ ํ•จ์ˆ˜ ์ž์ฒด์ด๋‹ค.

Passing event handlers as props

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์‹์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ง€์ •ํ•ด์•ผํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์ด๋•Œ, ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`Playing ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick}>
      Play "{movieName}"
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onClick={() => alert('Uploading!')}>
      Upload Image
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="Kiki's Delivery Service" />
      <UploadButton />
    </div>
  );
}
  • ์ด ๋•Œ ์ปดํฌ๋„ŒํŠธ์˜ props ๋ช…์€ onXXX๋กœ ํ•˜๊ณ  ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ ์ด๋ฆ„์€ handleXXX๋กœ ํ•˜๋Š” ๊ฒƒ์ด ๊ด€๋ก€์ด๋‹ค.

Naming event handler props

๊ด€๋ก€์ƒ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ props์€ on์œผ๋กœ ์‹œ์ž‘ํ•˜๊ณ  ๊ทธ ๋’ค์— ๋Œ€๋ฌธ์ž๊ฐ€ ์™€์•ผํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, onClick, onSmash์™€ ๊ฐ™์€ ์ด๋ฆ„์„ ๊ฐ€์ง„๋‹ค.

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Playing!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Play Movie
      </Button>
      <Button onClick={onUploadImage}>
        Upload Image
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

App ์ปดํฌ๋„ŒํŠธ๋Š” Toolbar์ปดํฌ๋„ŒํŠธ๊ฐ€ onPlayMovie ๋˜๋Š” onUploadImage๋กœ ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ• ์ง€ ์•Œ ํ•„์š”๊ฐ€ ์—†๋‹ค. ์ฆ‰, ํ•ด๋‹น ๋ถ€๋ถ„์€ Toolbar๊ฐ€ ๋งก์€ ์ผ์ด๋‹ค.

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์ ์ ˆํ•œ HTML ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜์ž! ex) <div onClick={handleClick></div>๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ <button onClick={handleClick></button>์ด ๋” ๋ช…ํ™•ํ•˜๋‹ค.

Event propagation

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ปดํฌ๋„ŒํŠธ์— ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์˜ ์ด๋ฒคํŠธ๋„ ํฌ์ฐฉํ•œ๋‹ค. ์ฆ‰, ๋ฒ„๋ธ”๋ง์ด ์ผ์–ด๋‚˜๋Š” ๊ฒƒ์ด๋‹ค.

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <button onClick={() => alert('Playing!')}>
        Play Movie
      </button>
      <button onClick={() => alert('Uploading!')}>
        Upload Image
      </button>
    </div>
  );
}
  • Toolbar๋ฅผ ํด๋ฆญํ•˜๋ฉด Playing!์ด ๋จผ์ € ์ถœ๋ ฅ๋˜๊ณ , ๊ทธ ๋‹ค์Œ์— Toolbar์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

JSX ํƒœ๊ทธ์—์„œ๋งŒ ์ž‘๋™ํ•˜๋Š” onScroll์„ ์ œ์™ธํ•œ ๋ชจ๋“  ์ด๋ฒคํŠธ๋Š” ์ „ํŒŒ๋œ๋‹ค.

Stopping propagation

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ด๋ฒคํŠธ ๊ฐ์ฒด๋ฅผ ์œ ์ผํ•œ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค. ๊ด€๋ก€์ƒ ์ด๋ฒคํŠธ ๊ฐ์ฒด๋Š” e ๋˜๋Š” event๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. ์ด ๋•Œ, e.stopPropagation()์„ ํ˜ธ์ถœํ•˜๋ฉด ์ด๋ฒคํŠธ ์ „ํŒŒ๋ฅผ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.

function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      e.stopPropagation();
      onClick();
    }}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <Button onClick={() => alert('Playing!')}>
        Play Movie
      </Button>
      <Button onClick={() => alert('Uploading!')}>
        Upload Image
      </Button>
    </div>
  );
}

Capture phase events

  • ๋ฆฌ์•กํŠธ์˜ ์ด๋ฒคํŠธ๋ฅผ ๋ฒ„๋ธ”๋ง์ด ์•„๋‹Œ ์บก์ฒ˜๋ง์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, onXXXCapture๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

Preventing default behavior

์ผ๋ถ€ ๋ธŒ๋ผ์šฐ์ € ์ด๋ฒคํŠธ์—๋Š” ์—ฐ๊ฒฐ๋œ ๊ธฐ๋ณธ ๋™์ž‘์ด ์žˆ๋‹ค. ex) <form>์˜ onSubmit์€ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•œ๋‹ค. ์ด ๋•Œ, e.preventDefault()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๊ธฐ๋ณธ ๋™์ž‘์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.

export default function Signup() {
  return (
    <form onSubmit={() => alert('Submitting!')}>
      <input />
      <button>Send</button>
    </form>
  );
}

e.stopPropagation(): ์ด๋ฒคํŠธ์˜ ์ „ํŒŒ๋ฅผ ๋ง‰๋Š”๋‹ค.
e.preventDefault(): ๋ช‡ ๊ฐ€์ง€ ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ ๋™์ž‘์„ ๋ง‰๋Š”๋‹ค.

Can event handlers have side effects?

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ๋Š” ์ˆ˜๋งŽ์€ side effect๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ Œ๋”๋ง ํ•จ์ˆ˜(์ปดํฌ๋„ŒํŠธ)์™€ ๋‹ฌ๋ฆฌ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ˆœ์ˆ˜ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค! ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ์ด๋ฒคํŠธ์— ๋”ฐ๋ผ ์ผ๋ถ€ ์ •๋ณด๋ฅผ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด ๋จผ์ € ์ •๋ณด๋ฅผ ์ €์žฅํ•  ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•˜๋‹ค. ์ด ๋•Œ, state๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

State: A Componentโ€™s Memory

์ปดํฌ๋„ŒํŠธ๋Š” ์ƒํ˜ธ ์ž‘์šฉ์˜ ๊ฒฐ๊ณผ๋กœ ํ™”๋ฉด์˜ ๋‚ด์šฉ์„ ๋ณ€๊ฒฝํ•œ๋‹ค. ์ด ๋•Œ, ์ปดํฌ๋„ŒํŠธ๋Š” ์ด์ „์— ์–ด๋–ค ์ƒํ˜ธ ์ž‘์šฉ์ด ์žˆ์—ˆ๋Š”์ง€ ๊ธฐ์–ตํ•ด์•ผ ํ•œ๋‹ค. ์ด๋Ÿฐ ์ข…๋ฅ˜์˜ ์ปดํฌ๋„ŒํŠธ ๋ณ„ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ state๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

When a regular variable isnโ€™t enough

import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

์œ„ ์˜ˆ์ œ์—์„œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด๋„ ํ™”๋ฉด์ด ๋ณ€ํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ์ง€์—ญ ๋ณ€์ˆ˜๋Š” ๋ Œ๋”๋ง ๊ฐ„์— ์œ ์ง€๋˜์ง€ ์•Š๋Š”๋‹ค. React๊ฐ€ ์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‘๋ฒˆ์งธ๋กœ ๋ Œ๋”๋งํ•  ๋•Œ ์ง€์—ญ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • ์ง€์—ญ ๋ณ€์ˆ˜๋ฅผ ๋ณ€๊ฒฝํ•ด๋„ ๋ Œ๋”๋ง์„ ๋ฐœ๋™์‹œํ‚ค์ง€ ์•Š๋Š”๋‹ค. ๋‹จ์ˆœ ์ง€์—ญ๋ณ€์ˆ˜์˜ ๋ณ€๊ฒฝ์œผ๋กœ React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ๊ฒƒ์„ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•œ๋‹ค.

์ด ๋•Œ, ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒˆ ๋ฐ์ดํ„ฐ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋ฉด 2๊ฐ€์ง€์˜ ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค.

  • ๋ Œ๋”๋ง ์‚ฌ์ด, ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•ด์•ผํ•œ๋‹ค.
  • ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ด์•ผ ํ•œ๋‹ค.

์œ„์˜ 2๊ฐ€์ง€๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด useState ํ›…์ด๋‹ค.

Meet your first Hook

React์—์„œ๋Š” use๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ›…(hook)์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. ํ›…์€ ๋ Œ๋”๋ง ์ค‘์ผ ๋•Œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํŠน๋ณ„ํ•œ ํ•จ์ˆ˜์ด๋‹ค.

ํ›…์€ ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ ํ˜น์€ ์ปค์Šคํ…€ ํ›…์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
ํ›…์€ ํ•จ์ˆ˜์ด์ง€๋งŒ ์ปดํฌ๋„ŒํŠธ์˜ ํ•„์š”์— ๋Œ€ํ•œ ๋ฌด์กฐ๊ฑด์ ์ธ ์„ ์–ธ์œผ๋กœ ์ƒ๊ฐํ•˜๋ฉด ํŽธํ•˜๋‹ค.
ํŒŒ์ผ ์ƒ๋‹จ์—์„œ ๋ชจ๋“ˆ์„ importํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ์ปดํฌ๋„ŒํŠธ ์ƒ๋‹จ์—์„œ React ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•œ๋‹ค.

Anatomy of useState

useState๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋Š” ๊ฑด, React ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ธฐ์–ตํ•˜๋„๋ก ์š”์ฒญํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

import { useState } from 'react';

const [index, setIndex] = useState(0);

์œ„์˜ ๊ฒฝ์šฐ, React๊ฐ€ index๋ฅผ ๊ธฐ์–ตํ•˜๊ธฐ๋ฅผ ์›ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋  ๋•Œ๋งˆ๋‹ค useState๋Š” 2๊ฐœ์˜ ๊ฐ’์„ ํฌํ•จํ•˜๋Š” ๋ฐฐ์—ด์„ ์ œ๊ณตํ•œ๋‹ค.

  • ์ €์žฅํ•œ ๊ฐ’์„ ๊ฐ€์ง„ state ๋ณ€์ˆ˜
  • state ๋ณ€์ˆ˜๋ฅผ ์—…๋ฐ์ดํŠธ ๋ฐ React๊ฐ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋„๋ก ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” setter ํ•จ์ˆ˜

useState์˜ ๋™์ž‘ ์›๋ฆฌ!

์œ„์˜ index state๋ฅผ ์˜ˆ์ œ๋กœ ๋“ค๋„๋ก ํ•ด๋ณด์ž

  1. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋ Œ๋”๋ง๋œ๋‹ค. index์˜ ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ 0์„ useState์— ์ „๋‹ฌ๋˜์—ˆ์œผ๋ฏ€๋กœ [0, setIndex]๊ฐ€ ๋ฐ˜ํ•œ๋œ๋‹ค. ์ด ๋•Œ, React๋Š” 0์„ ์ตœ์‹  state๋กœ ๊ธฐ์–ตํ•œ๋‹ค.
  2. state๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค. react๋Š” ์ด์ œ 1์„ ์ตœ์‹  state๋กœ ๊ธฐ์–ตํ•œ๋‹ค.
  3. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค. ๋ฌผ๋ก  ์—ฌ์ „ํžˆ useState(0)์ธ ์ฝ”๋“œ๊ฐ€ ์žˆ์ง€๋งŒ, index๋ฅผ 1๋กœ ์„ค์ •ํ•œ ๊ฒƒ์„ ๊ธฐ์–ตํ•ด์„œ [1, setIndex]๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Giving a component multiple state variables

ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ state ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๋•Œ, ์„œ๋กœ ์—ฐ๊ด€์ด ์žˆ๋Š”(๋‘๊ฐœ์˜ state ๋ณ€์ˆ˜๋ฅผ ์ž์ฃผ ํ•จ๊ป˜ ๋ณ€๊ฒฝํ•˜๋Š”) ๊ฒฝ์šฐ ํ•˜๋‚˜์˜ state ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

์–ด๋–ค state๋ฅผ ๋ฐ˜ํ™˜ํ• ์ง€ React๋Š” ์–ด๋–ป๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์„๊นŒ?

useStateํ˜ธ์ถœ์ด ์–ด๋–ค state ๋ณ€์ˆ˜๋ฅผ ์ฐธ์กฐํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋ฐ›์ง€ ๋ชปํ•œ๋‹ค. ์ด๋Ÿฐ โ€˜์‹๋ณ„์žโ€™๊ฐ€ ์—†๋Š”๋ฐ ์–ด๋–ค state ๋ณ€์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ• ์ง€ ์–ด๋–ป๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์„๊นŒ? ํ›…์€ ๋™์ผํ•œ ์ปดํฌ๋„ŒํŠธ์˜ ๋ชจ๋“  ๋ Œ๋”๋ง์—์„œ ์•ˆ์ •์ ์ด๊ณ  ๋™์ผํ•œ ์ˆœ์„œ๋กœ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•œ๋‹ค. ์ฆ‰, ํ›…์€ ํ•ญ์ƒ ๊ฐ™์€ ์ˆœ์„œ๋กœ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ตœ์ƒ์œ„ ๊ณ„์ธต์—์„œ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.(eslint-plugin-react-hooks์ด ์ด๋ฅผ ์ž˜ ์žก์•„์ค€๋‹ค.)

React๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ํ•œ ์Œ์˜ state์˜ ๋ฐฐ์—ด์„ ๊ฐ–๋Š”๋‹ค. ๋˜ํ•œ ๋ Œ๋”๋ง ์ „์— 0์œผ๋กœ ์„ค์ •๋œ ํ˜„์žฌ ์Œ ์ธ๋ฑ์Šค๋ฅผ ์œ ์ง€ํ•œ๋‹ค. useState๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค React๋Š” ๋‹ค์Œ state ์Œ์„ ์ œ๊ณตํ•˜๋ฉด์„œ index๋ฅผ 1 ์ฆ๊ฐ€์‹œํ‚จ๋‹ค.

let componentHooks = [];
let currentHookIndex = 0;

// How useState works inside React (simplified).
function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // This is not the first render,
    // so the state pair already exists.
    // Return it and prepare for next Hook call.
    currentHookIndex++;
    return pair;
  }

  // This is the first time we're rendering,
  // so create a state pair and store it.
  pair = [initialState, setState];

  function setState(nextState) {
    // When the user requests a state change,
    // put the new value into the pair.
    pair[0] = nextState;
    updateDOM();
  }

  // Store the pair for future renders
  // and prepare for the next Hook call.
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

์ฆ‰, [[state1, setState1], [state2, setState2], ...] ์ด๋Ÿฐ์‹์œผ๋กœ ์ €์žฅ๋˜์–ด์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

State is isolated and private

state๋Š” ์ปดํฌ๋„ŒํŠธ์— ์˜ํ•ด ์™„์ „ํžˆ ์บก์Šํ™”๋œ๋‹ค. ์ฆ‰, state๋Š” ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค.
์ด ๋ถ€๋ถ„์ด ๋ฐ”๋กœ ๋ชจ๋“ˆ ์ƒ๋‹จ์— ์„ ์–ธํ•˜๋Š” ์ผ๋ฐ˜ ๋ณ€์ˆ˜์™€ state์˜ ์ฐจ์ด์ ์ด๋‹ค. state๋Š” ํŠน์ • ํ•จ์ˆ˜ ํ˜ธ์ถœ์— ๋ฌถ์ด์ง€ ์•Š๊ณ , ์ฝ”๋“œ์˜ ํŠน์ • ์œ„์น˜์—๋„ ๋ฌถ์ด์ง€ ์•Š์œผ๋ฉด์„œ ํ™”๋ฉด์ƒ์˜ ํŠน์ • ์œ„์น˜์— ์ง€์—ญ์ ์ด๋‹ค. ๋˜ํ•œ, state๋Š” ์ด๋ฅผ ์„ ์–ธํ•œ ์ปดํฌ๋„ŒํŠธ ์™ธ์—๋Š” ์™„์ „ํžˆ ๋น„๊ณต๊ฐœ๋˜๊ณ , ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋‹ค.

๋งŒ์•ฝ state๋ฅผ ๋‘๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ณต์œ ํ•˜๊ณ  ๋™๊ธฐํ™”ํ•˜๋ ค๋ฉด ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ๋‘์–ด props๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค.

Render and Commit

์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๊ธฐ ์ „์— React์—์„œ ๋ Œ๋”๋ง์„ ํ•ด์•ผ ํ•œ๋‹ค.
๋ฆฌ์•กํŠธ๊ฐ€ ๊ณ ๊ฐ๋“ค์˜ ์š”์ฒญ์„ ๋ฐ›๊ณ  ์ฃผ๋ฌธ์„ ๊ฐ€์ ธ์˜ค๋Š” ์›จ์ดํ„ฐ๋ผ๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. ๊ทธ๋ฆฌ๊ณ  ์ฃผ๋ฐฉ์—์„œ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์žฌ๋ฃŒ๋กœ ์š”๋ฆฌ๋ฅผ ํ•˜๊ณ  ์žˆ๋‹ค. UI๋ฅผ ์š”์ฒญํ•˜๊ณ  ์„œ๋น™ํ•˜๋Š” ๊ณผ์ •์€ ์•„๋ž˜ 3๋‹จ๊ณ„๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.

  1. Triggering a render: ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง์„ ์‹œ์ž‘ํ•˜๋„๋ก ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค. => ์†๋‹˜์˜ ์ฃผ๋ฌธ์„ ์ฃผ๋ฐฉ์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค.
  2. Rendering the component: ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. => ์ฃผ๋ฐฉ ์ฃผ๋ฌธ์„ ๋ฐ›์•„ ์š”๋ฆฌํ•œ๋‹ค.
  3. Committing to the DOM: DOM์— ์ปค๋ฐ‹ํ•œ๋‹ค. => ์†๋‹˜์—๊ฒŒ ์š”๋ฆฌ๋ฅผ ๋‚ด๋†“๋Š”๋‹ค.

Step 1: Trigger a render

์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง์ด ์ผ์–ด๋‚˜๋Š” ๋ฐ์—๋Š” 2๊ฐ€์ง€์˜ ์ด์œ ๊ฐ€ ์žˆ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋Š” ๊ฒฝ์šฐ

์•ฑ์„ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ฒซ ๋ Œ๋”๋ง์„ ํ•ด์•ผ ํ•œ๋‹ค.

import Image from './Image.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Image />); // ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง
  • ์ปดํฌ๋„ŒํŠธ์˜ state๋‚˜ props(๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋œ state)๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜๋Š” ๊ฒฝ์šฐ

setState๋กœ state๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ์ถ”๊ฐ€๋กœ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ์˜ state๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋ Œ๋”๋ง์ด ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€๋œ๋‹ค. (์‹๋‹น์—์„œ ์†๋‹˜์ด ์ฒซ ์ฃผ๋ฌธ ์ดํ›„์— ์ถ”๊ฐ€ ์ฃผ๋ฌธ์„ ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค.)

Step 2: React renders your components

๋ Œ๋”๋ง์„ triggerํ•˜๋ฉด, ๋ฆฌ์•กํŠธ๋Š” ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ™”๋ฉด์— ํ‘œ์‹œํ•  ๋‚ด์šฉ์„ ํŒŒ์•…ํ•œ๋‹ค. ์ฆ‰, ๋ Œ๋”๋ง์€ ๋ฆฌ์•กํŠธ์—์„œ ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  • ์ฒซ ๋ Œ๋”๋ง์—์„œ๋Š” ๋ฃจํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
  • ์ดํ›„ ๋ Œ๋”๋ง์—์„œ๋Š” state์˜ ์—…๋ฐ์ดํŠธ์— ์˜ํ•ด ๋ Œ๋”๋ง์ด ๋ฐœ๋™๋œ ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

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

๋ Œ๋”๋ง์€ ํ•ญ์ƒ ์ˆœ์ˆ˜ ๊ณ„์‚ฐ์ด์–ด์•ผ ํ•œ๋‹ค.

  1. ๋™์ผํ•œ ์ž…๋ ฅ์— ๋Œ€ํ•ด์„œ ๋™์ผํ•œ ์ถœ๋ ฅ์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค.
  2. ํ•จ์ˆ˜ ์™ธ๋ถ€์˜ ์–ด๋–ค ๊ฒƒ๋„ ๋ณ€๊ฒฝํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.

Optimizing performance
์—…๋ฐ์ดํŠธ๋œ ์ปดํฌ๋„ŒํŠธ ๋‚ด์— ์žˆ๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ํ–‰์œ„๋Š” ๋น„ํšจ์œจ์ ์ด๋‹ค.(ํŠนํžˆ ์—…๋ฐ์ดํŠธ๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์œ„์— ์žˆ์„์ˆ˜๋ก!)
์„ฑ๊ธ‰ํ•˜๊ฒŒ ์ตœ์ ํ™”ํ•˜์ง€ ์•Š๋„๋ก ํ•ด๋ผ!

Step 3: React commits changes to the DOM

๋ฆฌ์•กํŠธ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง(ํ˜ธ์ถœ)ํ•œ ํ›„, DOM์„ ์ˆ˜์ •ํ•œ๋‹ค.

  1. ์ฒซ ๋ Œ๋”๋ง์˜ ๊ฒฝ์šฐ, ๋ฆฌ์•กํŠธ๋Š” appendChild() DOM api๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์„ฑํ•œ ๋ชจ๋“  DOM ๋…ธ๋“œ๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•œ๋‹ค.
  2. ์ดํ›„ ๋ Œ๋”๋ง(๋ฆฌ๋ Œ๋”๋ง)์˜ ๊ฒฝ์šฐ, ๋ฆฌ์•กํŠธ๋Š” ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ์ž‘์—…(๋ Œ๋”๋ง ์ค‘ ๊ณ„์‚ฐ๋œ ์ž‘์—…)์„ ์ ์šฉํ•˜์—ฌ DOM์ด ์ตœ์‹  ๋ Œ๋”๋ง ์ถœ๋ ฅ๊ณผ ์ผ์น˜์‹œํ‚จ๋‹ค.

๋ฆฌ์•กํŠธ๋Š” ๋ Œ๋”๋ง ์‚ฌ์ด์— ์ฐจ์ด๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ DOM ๋…ธ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ๋งค์ดˆ๋งˆ๋‹ค time์ด ๋ณ€๊ฒฝ๋  ๋•Œ(๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒ), input์˜ ๋‚ด๋ถ€์— ์žˆ๋Š” value state๋Š” ๊ทธ ๊ฐ’์ด ์‚ฌ๋ผ์ง€์ง€ ์•Š๊ณ  ์œ ์ง€๋œ๋‹ค.

export default function Clock({ time }) {
  return (
    <>
      <h1>{time}</h1>
      <input />
    </>
  );
}

Epilogue: Browser paint

๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋˜๊ณ  ๋ฆฌ์•กํŠธ๊ฐ€ DOM์„ ์—…๋ฐ์ดํŠธํ•œ ํ›„, ๋ธŒ๋ผ์šฐ์ €๋Š” ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆฐ๋‹ค. ์ด ๋ถ€๋ถ„์„ ๋ธŒ๋ผ์šฐ์ € ๋ Œ๋”๋ง์ด๋ผ๊ณ  ํ•˜์ง€๋งŒ, ํ˜„์žฌ ๊ณต์‹๋ฌธ์„œ์—์„œ๋Š” ํ˜ผ๋™์„ ํ”ผํ•˜๊ณ ์ž ํŽ˜์ธํŒ…์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

State as a Snapshot

state๋Š” ์Šค๋ƒ…์ƒท์ฒ˜๋Ÿผ ๋™์ž‘ํ•œ๋‹ค. state ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ด๋„ ์ด๋ฏธ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” state ๋ณ€์ˆ˜๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๊ณ , ๊ทธ ๋Œ€์‹  ๋ฆฌ๋ Œ๋”๋ง์ด ์‹คํ–‰๋œ๋‹ค.

import { useState } from 'react';

export default function Form() {
  const [isSent, setIsSent] = useState(false);
  const [message, setMessage] = useState('Hi!');
  if (isSent) {
    return <h1>Your message is on its way!</h1>
  }
  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      setIsSent(true);
      sendMessage(message);
    }}>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

function sendMessage(message) {
  // ...
}
  • onSubmit ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.
  • setIsSent(true)๊ฐ€ isSent๋ฅผ true๋กœ ์„ค์ •ํ•˜๊ณ  ์ƒˆ ๋ Œ๋”๋ง์„ ํ์— ๋Œ€๊ธฐ์‹œํ‚จ๋‹ค..
  • ๋ฆฌ์•กํŠธ๋Š” isSent ๊ฐ’์— ๋”ฐ๋ผ์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•œ๋‹ค.

Rendering takes a snapshot in time

๋ Œ๋”๋ง์ด๋ž€ ๋ฆฌ์•กํŠธ๊ฐ€ ์ปดํฌ๋„ŒํŠธ, ์ฆ‰ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ํ•ด๋‹น ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” JSX๋Š” ๊ทธ ์ˆœ๊ฐ„์˜ UI ์Šค๋ƒ…์ƒท๊ณผ ๊ฐ™๋‹ค.

React๊ฐ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•  ๋•Œ,

  • ๋ฆฌ์•กํŠธ๊ฐ€ ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•œ๋‹ค.
  • ํ•จ์ˆ˜๊ฐ€ ์ƒˆ๋กœ์šด JSX ์Šค๋ƒ…์ƒท์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • ์ดํ›„, ๋ฆฌ์•กํŠธ๋Š” ์ด์ „ ์Šค๋ƒ…์ƒท๊ณผ ์ƒˆ ์Šค๋ƒ…์ƒท์„ ๋น„๊ตํ•˜๊ณ  ์ผ์น˜ํ•˜๋„๋ก ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.

์ปดํฌ๋„ŒํŠธ์˜ ๋ฉ”๋ชจ๋ฆฌ๋กœ์„œ state๋Š” ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜๋œ ํ›„ ์‚ฌ๋ผ์ง€๋Š” ์ผ๋ฐ˜ ๋ณ€์ˆ˜์™€๋Š” ๋‹ค๋ฅด๋‹ค. state๋Š” ํ•จ์ˆ˜ ์™ธ๋ถ€์— ์กด์žฌํ•˜๋Š” ๋ณ€์ˆ˜์ฒ˜๋Ÿผ React ์ž์ฒด์— ์กด์žฌํ•œ๋‹ค.

๋ฆฌ์•กํŠธ๊ฐ€ state๋ฅผ ๋ฉ”๋ชจ

์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ๋ฒ„ํŠผ์„ 1๋ฒˆ ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค, number๋Š” 3์”ฉ ์ฆ๊ฐ€ํ•  ๊ฒƒ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ number๋Š” 1์”ฉ ์ฆ๊ฐ€ํ•œ๋‹ค.

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        console.log(number);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

setState๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ด์ „ ๋ Œ๋”๋ง์— ๋Œ€ํ•ด์„œ๋งŒ state๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค. ์ฆ‰, ์•„๋ฌด๋ฆฌ setNumber๋ฅผ ํ˜ธ์ถœํ–ˆ์–ด๋„ ์ด์ „ ๋ Œ๋”๋ง์—์„œ number๋Š” 0์ด๊ธฐ ๋•Œ๋ฌธ์— 1๋งŒ ์ฆ๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๊ฐ„์— console.log๋ฅผ ์ฐ์–ด๋ณด๋ฉด 0์ด ์ฐํžˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

State over time

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณด๋ฉด 3์ดˆ ๋’ค alert ๊ฐ’์œผ๋กœ๋„ 0์ด ์ฐํžˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setTimeout(() => {
          alert(number);
        }, 3000);
      }}>+5</button>
    </>
  )
}

์™œ ๊ทธ๋Ÿด๊นŒ? ์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋Š” ์‹œ์ ์—์„œ number๋Š” 0์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. setTimeout์€ 3์ดˆ ๋’ค์— ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์—, 3์ดˆ ๋’ค์— ์‹คํ–‰๋˜๋Š” alert์—์„œ๋„ number๊ฐ€ 0์ด๋‹ค.(3์ดˆ ๋’ค์— number๊ฐ€ 0์ธ ์Šค๋ƒ…์ƒท์ด ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.)
๋ฆฌ์•กํŠธ๋Š” ํ•˜๋‚˜์˜ ๋ Œ๋”๋ง ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด์—์„œ state๊ฐ’์„ ๊ณ ์ •์œผ๋กœ ์œ ์ง€ํ•œ๋‹ค. ์ฆ‰, ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ state๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ๊ฑฑ์ •ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

Queueing a Series of State Updates

state ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜๋ฉด ๋‹ค์Œ ๋ Œ๋”๋ง์ด ํ์— ๋“ค์–ด๊ฐ„๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ฒฝ์šฐ์— ๋”ฐ๋ผ์„œ ๋‹ค์Œ ๋ Œ๋”๋ง์„ ํ์— ์ „๋‹ฌํ•˜๊ธฐ ์ „์—, state์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ๋‹ค.

React batches state updates

๋ฆฌ์•กํŠธ๋Š” state ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๊ธฐ ์ „์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ ๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. ๋•Œ๋ฌธ์— ๋ฆฌ๋ Œ๋”๋ง์€ ๋ชจ๋“  setState ํ˜ธ์ถœ์ด ์™„๋ฃŒ๋œ ์ดํ›„์—๋งŒ ์ผ์–ด๋‚œ๋‹ค. ์ด๋Š” ์Œ์‹์ ์—์„œ ์ฃผ๋ฌธ์„ ๋ฐ›๋Š” ์›จ์ดํ„ฐ์™€ ๊ฐ™๋‹ค. ์šฐ๋ฆฌ๊ฐ€ a,b,c๋ผ๋Š” ์Œ์‹์„ ์ฃผ๋ฌธํ•œ๋‹ค๋ฉด ์›จ์ดํ„ฐ๋Š” ์šฐ๋ฆฌ๊ฐ€ a๋ฅผ ๋งํ•˜์ž๋งˆ์ž ์ฃผ๋ฌธ์„ ์ „๋‹ฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹ ์— ์šฐ๋ฆฌ๊ฐ€ ์ฃผ๋ฌธ์„ ์™„๋ฃŒํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ๊ฐ€ ์ฃผ๋ฌธ์„ ์™„๋ฃŒํ•˜๋ฉด ์›จ์ดํ„ฐ๋Š” ์ฃผ๋ฌธ์„ ์ „๋‹ฌํ•œ๋‹ค. ์‹ฌ์ง€์–ด ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ์ฃผ๋ฌธ๊นŒ์ง€ ํ•œ๋ฒˆ์— ๋ฐ›์•„์„œ ์ „๋‹ฌํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋‚˜์˜จ ์—ฌ๋Ÿฌ state ๋ณ€์ˆ˜๋ฅผ ์—…๋ฐ์ดํŠธํ•จ์— ๋”ฐ๋ผ ๋ฆฌ๋ Œ๋”๋ง์„ triggerํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ง์€ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ๊ทธ ๋‚ด๋ถ€์— ์žˆ๋Š” ์ฝ”๋“œ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ UI๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

๋ฐ˜๋ฉด ๋ฆฌ์•กํŠธ๋Š” ํด๋ฆญ๊ณผ ๊ฐ™์€ ์—ฌ๋Ÿฌ ์˜๋„์ ์ธ ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด ์ผ๊ด„ ์ฒ˜๋ฆฌ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ฐ ์ด๋ฒคํŠธ๋Š” ๊ฐœ๋ณ„์ ์œผ๋กœ ์ฒ˜๋ฆฌ ๋œ๋‹ค.

Updating the same state multiple times before the next render

๋‹ค์Œ ๋ Œ๋”๋ง ์ „์— ๋™์ผํ•œ state ๋ณ€์ˆ˜๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ์—…๋ฐ์ดํŠธ ํ•˜๊ณ  ์‹ถ์„ ๋•Œ, setState((prev) => prev + 1)์™€ ๊ฐ™์ด ํ•จ์ˆ˜๋ฅผ ์ธ์ž๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด ๋œ๋‹ค. ์ด๋Š” ์ด์ „ ๋ Œ๋”๋ง์—์„œ์˜ state๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ state ํ์˜ ์ด์ „ state๋ฅผ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•˜๋‹ค.

์ฐธ๊ณ : setState(5) ๋˜ํ•œ setState(prev => 5)์ฒ˜๋Ÿผ ๋™์ž‘ํ•œ๋‹ค.

Naming conventions

์—…๋ฐ์ดํ„ฐ ํ•จ์ˆ˜ ์ธ์ˆ˜(์ฝœ๋ฐฑ ํ•จ์ˆ˜)์˜ ์ด๋ฆ„์€ ํ•ด๋‹น state ๋ณ€์ˆ˜์˜ ์ฒซ ๊ธ€์ž๋กœ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋‹ค.

setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);