๐Ÿ“Ž React

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

Get Started - Quick start

์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒƒ์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ๋‹ค.

  • How to create and nest components - ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•
  • How to add markup and styles - ๋งˆํฌ์—…๊ณผ ์Šคํƒ€์ผ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • How to display data - ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ฐฉ๋ฒ•
  • How to render conditions and lists - ์กฐ๊ฑด๊ณผ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ๋ฒ•
  • How to respond to events and update the screen - ์ด๋ฒคํŠธ์— ์‘๋‹ตํ•˜๊ณ  ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•
  • How to share data between components - ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋ฐฉ๋ฒ•

Creating and nesting components

function MyButton() {
  return (
    <button>I'm a button</button>
  );
}

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      {/* ์•„๋ž˜์™€ ๊ฐ™์ด MyButton ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. */}
      <MyButton />
    </div>
  );
}

React Component๋Š” ๋ฐ˜๋“œ์‹œ ์ฒซ๊ธ€์ž๊ฐ€ ๋Œ€๋ฌธ์ž์—ฌ์•ผ ํ•œ๋‹ค.(HTML ํƒœ๊ทธ๊ฐ€ ์†Œ๋ฌธ์ž์ธ ๋ฐ˜๋ฉด์—)

Writing markup with JSX

function AboutPage() {
  return (
    <>
      <h1>About</h1>
      <p>Hello there.<br />How do you do?</p>
    </>
  );
}

JSX๋Š” HTML๋ณด๋‹ค ๋” ์—„๊ฒฉํ•˜๋‹ค. ๋ฐ˜๋“œ์‹œ ๋‹ซ๋Š” ํƒœ๊ทธ(/)๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค. ๋˜ํ•œ React Component๋Š” ํ•œ๋ฒˆ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ JSX ํƒœ๊ทธ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†๋‹ค. ์ฆ‰, ์ตœ์ƒ์œ„์— ํ•˜๋‚˜์˜ ํƒœ๊ทธ๋งŒ ์กด์žฌํ•ด์•ผ ํ•œ๋‹ค. ์ฃผ๋กœ <div></div> ํ˜น์€ <></>(Fragment) ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Adding styles

<img className="avatar" />
.avatar {
    border-radius: 50%;
}

html ํƒœ๊ทธ์—์„œ์˜ class์™€ ๋‹ค๋ฅด๊ฒŒ className์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋Š” class๊ฐ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ์˜ˆ์•ฝ์–ด์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
๋ฆฌ์•กํŠธ๋Š” CSS ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•˜๋Š” ํŠน์ • ๋ฐฉ๋ฒ•์„ ๊ทœ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ฒŒ๋Š” html์— link ํƒœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๊ณ  ๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ css๋ฅผ importํ•˜๊ธฐ๋„ ํ•œ๋‹ค. ํ˜น์€ CSS-in-JS ๋ฐฉ์‹์œผ๋กœ ์Šคํƒ€์ผ์„ ์ž‘์„ฑํ•˜๊ธฐ๋„ ํ•œ๋‹ค. ์ฐธ๊ณ ๋กœ create-react-app์—๋Š” tailwindCSS๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.

Displaying data

function MyComponent() {
  const user = {
    name: 'jayden',
    imageUrl: 'https://~~~'
  };
  return (
    <>
      <h1>{user.name}</h1>
      <img src={user.imageUrl} />
    </>
  );
}

JSX๋Š” JavaScript ์•ˆ์—์„œ ๋งˆํฌ์—…์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค. ๋˜ํ•œ {}๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์‹œ JS ์ฝ”๋“œ๋กœ ๋Œ์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.(JS ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ค‘๊ด„ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๋ฐ˜๋“œ์‹œ ๊ธฐ์–ตํ•˜์ž.)

const user = {
  name: 'Hedy Lamarr',
  imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
  imageSize: 90,
};

export default function Profile() {
  return (
    <>
      <h1>{user.name}</h1>
      <img
        className="avatar"
        src={user.imageUrl}
        alt={'Photo of ' + user.name}
        {/* ์•„๋ž˜์™€ ๊ฐ™์ด ์ค‘๊ด„ํ˜ธ 2๊ฐœ๋ฅผ ๋งŽ์ด ํ—ท๊ฐˆ๋ คํ•˜๋Š”๋ฐ, ๋ฐ”๊นฅ ์ค‘๊ด„ํ˜ธ๋Š” JS ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•จ์ด๊ณ  ์•ˆ์ชฝ ์ค‘๊ด„ํ˜ธ๋Š” ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด ์ค‘๊ด„ํ˜ธ์ด๋‹ค. */}
        style={{
          width: user.imageSize,
          height: user.imageSize
        }}
      />
    </>
  );
}

Conditional rendering

let content;
if (isLoggedIn) {
  content = <AdminPanel />;
} else {
  content = <LoginForm />;
}
return (
  <div>
    {content}
  </div>
);
<div>
  {isLoggedIn ? (
    <AdminPanel />
  ) : (
    <LoginForm />
  )}
</div>
<div>
  {/* ํ•„์ˆ˜๋Š” ์•„๋‹ˆ์ง€๋งŒ &&์™€ ||๋ฅผ ํ†ตํ•œ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์„ ์ž์ฃผ ์‚ฌ์šฉํ•œ๋‹ค. */}
  {isLoggedIn && <AdminPanel />}
</div>

Rendering lists

const products = [
  { title: 'Cabbage', id: 1 },
  { title: 'Garlic', id: 2 },
  { title: 'Apple', id: 3 },
];

const listItems = products.map(product =>
  <li key={product.id}>
    {product.title}
  </li>
);

return (
  <ul>{listItems}</ul>
);

listItems๋Š” <li></li> ํ˜•ํƒœ์˜ ๋ฐฐ์—ด์ด๋‹ค. ์ด ๋ฐฐ์—ด ํ˜•ํƒœ๋ฅผ JSX์— ์ „๋‹ฌํ•˜๋ฉด ์•Œ์•„์„œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ฆฌ์ŠคํŠธ ํƒœ๊ทธ๋กœ ๋ Œ๋”๋ง๋œ๋‹ค.

์ด๋ ‡๊ฒŒ ๋ฐฐ์—ด์„ ์ „๋‹ฌํ•  ๋•Œ๋Š” key prop์„ ๊ผญ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์ด๋Š” React๊ฐ€ ๊ฐ๊ฐ์˜ ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

Responding to events

function MyButton() {
  function handleClick() {
    alert('You clicked me!');
  }

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

JSX์—์„œ ์–ด๋–ค ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ „๋‹ฌํ•  ๋•Œ, ์•ฝ์†๋œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด onClick, onMouseOver ๋“ฑ์ด ์žˆ๋‹ค. ๋˜ํ•œ, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ํ•จ์ˆ˜๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค.(ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ํ•จ์ˆ˜ ์ž์ฒด๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์  ์œ ์˜!)

Updating the screen

import { useState } from 'react';

function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Clicked {count} times
    </button>
  );
}

์–ด๋–ค ๊ฐ’(์ƒํƒœ; state)์„ ๊ธฐ์–ตํ•˜๊ณ  ์ด ๊ฐ’์˜ ๋ณ€ํ™”์— ๋”ฐ๋ผ UI๋ฅผ ๋‹ค๋ฅด๊ฒŒ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด useState ํ›…์„ ์‚ฌ์šฉํ•œ๋‹ค. useState๋Š” ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, ์ฒซ๋ฒˆ์งธ ์›์†Œ๋Š” ์ƒํƒœ ๊ฐ’์ด๊ณ  ๋‘๋ฒˆ์งธ ์›์†Œ๋Š” ์ƒํƒœ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค. ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ƒํƒœ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๊ณ  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋œ๋‹ค.

import { useState } from 'react';

export default function MyApp() {
  return (
    <div>
      <h1>Counters that update separately</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}

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

Using Hooks

use๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ•จ์ˆ˜๋“ค์„ Hooks๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. useState๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ Hook์ด๋‹ค. ์ด ์™ธ์—๋„ useEffect, useContext, useReducer ๋“ฑ์ด ์žˆ๋‹ค.(์ฐธ๊ณ ) ๋˜ํ•œ, ์กด์žฌํ•˜๋Š” ํ›…๋“ค์„ ์กฐํ•ฉํ•˜์—ฌ ์ปค์Šคํ…€ ํ›…์„ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๋‹ค.

ํ›…๋“ค์€ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋“ค๋ณด๋‹ค ๋” ์ œํ•œ์ ์ด๋‹ค. ๋ฐ˜๋“œ์‹œ ์ปดํฌ๋„ŒํŠธ(ํ˜น์€ ์ปค์Šคํ…€ ํ›…)์˜ ์ตœ์ƒ๋‹จ์—์„œ ํ˜ธ์ถœํ•ด์•ผ๋งŒ ํ•œ๋‹ค. ๋˜ํ•œ ์กฐ๊ฑด๋ฌธ์ด๋‚˜ ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ ํ˜ธ์ถœํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ์ด๋Š” ํ›…์ด ์ปดํฌ๋„ŒํŠธ์˜ ์ƒํƒœ๋ฅผ ๊ธฐ์–ตํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋งŒ์•ฝ ์กฐ๊ฑด๋ฌธ์ด๋‚˜ ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ ํ˜ธ์ถœํ•˜๋ฉด, ์ปดํฌ๋„ŒํŠธ์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋„ ํ›…์ด ์ด๋ฅผ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์กฐ๊ฑด๋ฌธ์ด๋‚˜ ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ ํ›…์„ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์กฐ๊ฑด๋ฌธ์ด๋‚˜ ๋ฐ˜๋ณต๋ฌธ์„ ์ปดํฌ๋„ŒํŠธ๋กœ ์ถ”์ถœํ•œ ํ›„ ํ›…์„ ํ˜ธ์ถœํ•˜๋ฉด ๋œ๋‹ค.

Sharing data between components

์•ž์—์„œ ์‚ดํŽด๋ดค๋“ฏ์ด ๋™์ผํ•œ ์ปดํฌ๋„ŒํŠธ์—ฌ๋„ ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋Š” ๋…๋ฆฝ์ ์ธ ์ƒํƒœ๋ฅผ ๊ฐ€์ง„๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ปดํฌ๋„ŒํŠธ ๊ฐ„์— ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?

๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ๋ฐฉ๋ฒ•์€ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ณ ์ž ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์—์„œ state๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

export default function MyApp() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <h1>Counters that update together</h1>
      <MyButton count={count} onClick={handleClick} />
      <MyButton count={count} onClick={handleClick} />
    </div>
  );
}

function MyButton({ count, onClick }) {
  return (
    <button onClick={onClick}>
      Clicked {count} times
    </button>
  );
}

์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ lifting state up(์ƒํƒœ ๋Œ์–ด์˜ฌ๋ฆฌ๊ธฐ)๋ผ๊ณ  ํ•œ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์ปดํฌ๋„ŒํŠธ ๊ฐ„์— ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์ง€๋งŒ, ์ปดํฌ๋„ŒํŠธ ๊ฐ„์˜ ๊ด€๊ณ„๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•ด ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์ƒ๊ธด๋‹ค.

Get Started - Quick start - Tutorial: Tic-Tac-Toe

ํŠœํ† ๋ฆฌ์–ผ์€ ํ•˜๋‚˜ํ•˜๋‚˜ ๋”ฐ๋ผ๊ฐ€๊ธฐ ๋ณด๋‹จ ์ „์ฒด ์ฝ”๋“œ๋ฅผ ๋‘๊ณ  ์ฐธ๊ณ ํ• ๋งŒํ•œ ๋‚ด์šฉ์„ ์ฃผ์„์œผ๋กœ ์ž‘์„ฑํ•˜๋ ค ํ•œ๋‹ค.

import { useState } from 'react';

function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

function Board({ xIsNext, squares, onPlay }) {
  // setState๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•  ๋•Œ๋Š” ๋ณดํ†ต `handle~` ์ปจ๋ฒค์…˜์„ ๋”ฐ๋ฅธ๋‹ค.
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        {/* props๋กœ ์ „๋‹ฌ๋˜๋Š” setState๊ด€๋ จ ํ•จ์ˆ˜(์ฃผ๋กœ handle~ ํ˜•ํƒœ)๋Š” on~ ๋„ค์ž„ ์ปจ๋ฒค์…˜์„ ๊ฐ€์ง„ prop์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค. */}
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

export default function Game() {
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [currentMove, setCurrentMove] = useState(0);
  const xIsNext = currentMove % 2 === 0;
  const currentSquares = history[currentMove];

  function handlePlay(nextSquares) {
    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
    setHistory(nextHistory);
    setCurrentMove(nextHistory.length - 1);
  }

  function jumpTo(nextMove) {
    setCurrentMove(nextMove);
  }

  const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
      description = 'Go to move #' + move;
    } else {
      description = 'Go to game start';
    }
    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );
  });

  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

Note 1

The DOM <button> elementโ€™s onClick attribute has a special meaning to React because it is a built-in component. For custom components like Square, the naming is up to you. You could give any name to the Squareโ€™s onSquareClick prop or Boardโ€™s handleClick function, and the code would work the same. In React, itโ€™s conventional to use onSomething names for props which represent events and handleSomething for the function definitions which handle those events.

props๋กœ ์ „๋‹ฌ ์‹œ, onSomething ํ˜•ํƒœ / ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉ ์‹œ, handleSomething ํ˜•ํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ปจ๋ฒค์…˜์ด๋‹ค.

Note 2

Itโ€™s strongly recommended that you assign proper keys whenever you build dynamic lists. If you donโ€™t have an appropriate key, you may want to consider restructuring your data so that you do. If no key is specified, React will report an error and use the array index as a key by default. Using the array index as a key is problematic when trying to re-order a listโ€™s items or inserting/removing list items. Explicitly passing key={i} silences the error but has the same problems as array indices and is not recommended in most cases. Keys do not need to be globally unique; they only need to be unique between components and their siblings.

๋™์ ์œผ๋กœ li ํƒœ๊ทธ๋“ค์„ ๋งŒ๋“ค ๋•Œ, ๋ฐ˜๋“œ์‹œ key๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค. key๊ฐ€ ์—†์„ ๊ฒฝ์šฐ, React๋Š” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  ๋ฐฐ์—ด์˜ ์ธ๋ฑ์Šค๋ฅผ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. ๋ฐฐ์—ด์˜ ์ธ๋ฑ์Šค๋ฅผ key๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ์˜ ์ˆœ์„œ๋ฅผ ์žฌ์ •๋ ฌํ•˜๊ฑฐ๋‚˜ ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ์„ ์‚ฝ์ž…/์ œ๊ฑฐํ•  ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. key๋Š” ์ „์—ญ์ ์œผ๋กœ ๊ณ ์œ ํ•  ํ•„์š”๋Š” ์—†๊ณ , ์ปดํฌ๋„ŒํŠธ์™€ ํ˜•์ œ ์‚ฌ์ด์—์„œ๋งŒ ๊ณ ์œ ํ•˜๋ฉด ๋œ๋‹ค.

Get Started - Quick start - Thinking in React

๋ฆฌ์•กํŠธ๋Š” ๋‹น์‹ ์ด ๋ณด๊ธธ ์›ํ•˜๋Š” ๋””์ž์ธ๊ณผ ๋‹น์‹ ์ด ๋งŒ๋“ค๊ธฐ ์›ํ•˜๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๊ด€ํ•œ ๋‹น์‹ ์˜ ์ƒ๊ฐ์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค. ์ฒ˜์Œ ๋ฆฌ์•กํŠธ๋กœ UI๋ฅผ ๋งŒ๋“ค๋ฉด ๊ทธ๊ฒŒ ๋ฐ”๋กœ component๊ฐ€ ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‹น์‹ ์€ ์ปดํฌ๋„ŒํŠธ ๊ฐ๊ฐ์— ์‹œ๊ฐ์ ์ธ state๋ฅผ ๋‹ค๋ฅด๊ฒŒ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค.

Start with the mockup

์•„๋ž˜์™€ ๊ฐ™์€ ๋ชฉ์—… ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž.

[
  { category: "Fruits", price: "$1", stocked: true, name: "Apple" },
  { category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" },
  { category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" },
  { category: "Vegetables", price: "$2", stocked: true, name: "Spinach" },
  { category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" },
  { category: "Vegetables", price: "$1", stocked: true, name: "Peas" }
]

์œ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ํ‘œํ˜„ํ•˜๊ณ  ์‹ถ๋‹ค.(๊ทธ๋ฆฌ๊ณ  ์‹ถ๋‹ค.)

mock ui

๋ฆฌ์•กํŠธ์—์„œ ์œ„์˜ UI๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด, ๋‹ค์Œ 5๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ฅผ ๊ฒƒ์ด๋‹ค.

Step 1: Break the UI into a component hierarchy

๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ, ์ƒ์œ„/ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•ด์„œ ๋ฐ•์Šค๋ฅผ ๊ทธ๋ฆฌ๊ณ  ์ด๋ฆ„์„ ๋ถ™์ธ๋‹ค.

๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

  • Programming: ์ƒˆ๋กœ์šด ํ•จ์ˆ˜ ํ˜น์€ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ ๋‹ค๋Š” ๊ด€์ . ๊ฐ ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์— ์ž…๊ฐํ•˜์—ฌ ๋‚˜๋ˆŒ ๊ฒƒ์ด๋‹ค. ์ด์ƒ์ ์œผ๋กœ๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ํ•˜๋‚˜์˜ ์ผ๋งŒ ํ•˜๋Š” ๊ฒŒ ์ข‹๋‹ค.
  • CSS: class ์„ ํƒ์ž์— ๋Œ€ํ•ด ๊ณ ๋ คํ•˜๋ฉด์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚˜๋ˆˆ๋‹ค.
  • Design: ๋””์ž์ธ ์ธต์„ ์กฐ์งํ•˜๋Š” ๊ด€์ .

JSON์ด ์ž˜ ๊ตฌ์„ฑ๋˜์–ด์žˆ์„์ˆ˜๋ก UI ๊ตฌ์กฐ์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งคํ•‘๋œ๋‹ค.(UI์™€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋น„๊ต์  ์ผ๋Œ€์ผ ๋Œ€์‘์ด ๋œ๋‹ค๋Š” ์ด์•ผ๊ธฐ) UI์™€ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์ด ๊ฐ™์€ ์ •๋ณด๋ฅผ ๊ฐ€์ง„ ์•„ํ‚คํ…์ ธ์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ปดํฌ๋„ŒํŠธ๋กœ UI๋“ค์„ ๋‚˜๋ˆ„๊ณ  ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹น์‹ ์˜ ๋ฐ์ดํ„ฐ ๊ฐ๊ฐ์— ์–ด๋–ป๊ฒŒ ๋งค์น˜๋˜๋Š”์ง€ ๋‚˜๋ˆ ๋ณด์•„๋ผ.

mock ui components
  • FilterableProductTable (grey): ์ „์ฒด App
    • SearchBar (blue): ์œ ์ € input์„ ๋ฐ›๋Š”๋‹ค.
    • ProductTable (lavender): ์œ ์ € input์— ๋”ฐ๋ผ ๋ฆฌ์ŠคํŠธ๋ฅผ ํ•„ํ„ฐ๋งํ•˜์—ฌ ๋ณด์—ฌ์ค€๋‹ค.
      • ProductCategoryRow (green): ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ์ œ๋ชฉ์„ ๋ณด์—ฌ์ค€๋‹ค.
      • ProductRow (yellow): ๊ฐ ์ƒํ’ˆ์˜ ํ–‰์„ ๋ณด์—ฌ์ค€๋‹ค.

์œ„์˜ ์ด๋ฏธ์ง€์—์„œ ๋ณด๋ผ์ƒ‰ ๋ถ€๋ถ„์˜ header ๊ฒฉ์ธ Name๊ณผ Price๋Š” ๋”ฐ๋กœ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚˜๋ˆ„์ง€ ์•Š์•˜๋‹ค. ์ด๋Š” ์ˆœ์ „ํžˆ ๋ณธ์ธ์˜ ๋ชซ์ด๋‹ค. ๋”ฐ๋กœ component๋กœ ๋นผ์ฃผ์–ด๋„ ๋˜๊ณ  ์ง€๊ธˆ์ฒ˜๋Ÿผ ProductTable๋กœ ๋ฌถ์–ด๋„ ๋œ๋‹ค. ๋‹จ, ๋งŒ์•ฝ Name๊ณผ Price ๋ถ€๋ถ„์ด ๋ณต์žกํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ ์‚ฌ์šฉ๋œ๋‹ค๋ฉด ๋”ฐ๋กœ ๋นผ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Step 2: Build a static version in React

์ด์ œ ์ปดํฌ๋„ŒํŠธ ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ๊ฐ–๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฆฌ์•กํŠธ๋กœ UI๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์–ด๋– ํ•œ ์ƒํ˜ธ์ž‘์šฉ ์—†์ด ๋‹จ์ˆœํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ •์ ์ธ ๋ฒ„์ „์˜ ์•ฑ์„ ๋จผ์ € ๊ตฌํ˜„ํ•˜๊ณ  ์ถ”ํ›„์— ์ƒํ˜ธ์ž‘์šฉ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋‹ค. ์ •์ ์ธ ๋ฒ„์ „์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํƒ€์ดํ•‘์€ ๋งŽ์ด, ์ƒ๊ฐ์€ ์ ๊ฒŒ ํ•„์š”ํ•˜๋‹ค. ๋ฐ˜๋ฉด ์ƒํ˜ธ์ž‘์šฉ์„ ๋”ํ•˜๋Š” ๊ฒƒ์€ ์ ์€ ํƒ€์ดํ•‘, ๋งŽ์€ ์ƒ๊ฐ์„ ์š”๊ตฌํ•œ๋‹ค.

๋‹น์‹ ์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋กœ๋ถ€ํ„ฐ ์ •์ ์ธ ๋ฒ„์ „์˜ ์•ฑ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ, ๋‹น์‹ ์€ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ณ  props๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š” components๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๊ฒƒ์ด๋‹ค. Props๋Š” ๋ถ€๋ชจ์—์„œ ์ž์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. (๋‹น์‹ ์ด state์— ์ต์ˆ™ํ•˜๋”๋ผ๋„, ์ •์ ์ธ ๋ฒ„์ „์˜ ์•ฑ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” state๋Š” ์ผ์ฒด ์‚ฌ์šฉํ•˜์ง€ ๋ง์•„๋ผ.) state๋Š” ์˜ค์ง ์ƒํ˜ธ์ž‘์šฉ์„ ์œ„ํ•ด์„œ ์กด์žฌํ•œ๋‹ค.(์ฆ‰, ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ•˜๋Š” ๋ฐ์ดํ„ฐ) ๋•Œ๋ฌธ์— ์ •์ ์ธ ๋ฒ„์ „์˜ ์•ฑ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” state๋Š” ํ•„์š”์น˜ ์•Š๋‹ค.

๋‹น์‹ ์€ ๊ฑฐ๋Œ€ํ•œ ์ปดํฌ๋„ŒํŠธ๋ถ€ํ„ฐ ๊ฐœ๋ฐœํ•˜๊ธฐ ์‹œ์ž‘ํ•˜๋Š” Top-down ๋ฐฉ์‹๊ณผ ์ž‘์€ ์ปดํฌ๋„ŒํŠธ๋ถ€ํ„ฐ ๊ฐœ๋ฐœํ•˜๊ธฐ ์‹œ์ž‘ํ•˜๋Š” Bottom-up ๋ฐฉ์‹ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ Top-down ๋ฐฉ์‹์ด ๋” ์‰ฝ๋‹ค. ํ•œํŽธ, ๊ฑฐ๋Œ€ํ•œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Bottom-up ๋ฐฉ์‹์ด ๋” ์‰ฝ๋‹ค.(์ž‘์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐœ๋ฐœํ•˜๊ณ  ์ด๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ๊ฑฐ๋Œ€ํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.)

code-sandbox ์ฐธ๊ณ 

์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์„ one-way data flow๋ผ๊ณ  ํ•œ๋‹ค. ์ตœ์ƒ๋‹จ ์ปดํฌ๋„ŒํŠธ์—์„œ ์•„๋ž˜๋กœ๋งŒ data flow๊ฐ€ ์ด๋ฃจ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.(props๋ฅผ ํ†ตํ•ด)

์—ฌ๊ธฐ์„œ ํ•ต์‹ฌ์€ state ์—†์ด ์˜ค์ง props๋งŒ์„ ์ด์šฉํ•ด components์˜ data flow๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ค!

Step 3: Find the minimal but complete representation of UI state

UI๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ, ๋‹น์‹ ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹น์‹ ์˜ data model์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์•ผํ•œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด state๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

state๋ฅผ ๋‹น์‹ ์˜ ์•ฑ์ด ๊ธฐ์–ตํ•  ํ•„์š”๊ฐ€ ์žˆ๋Š” ์ตœ์†Œํ•œ์˜ ๋ณ€ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋กœ ์ƒ๊ฐํ•ด๋ผ. state๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š”๋ฐ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์›์น™์€ DRY(Donโ€™t Repeat Yourself)ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋‹น์‹ ์˜ ์•ฑ์ด ํ•„์š”๋กœ ํ•˜๋Š” state๋ฅผ ์ ˆ๋Œ€์ ์œผ๋กœ ์ตœ์†Œํ™”ํ•˜๊ณ  ๊ทธ ์™ธ์˜ ๋ชจ๋“  ๊ฒƒ๋“ค์€ ์š”๊ตฌ๋  ๋•Œ ๊ณ„์‚ฐํ•ด๋ผ. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹น์‹ ์ด ์‡ผํ•‘ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ, ๋‹น์‹ ์€ items๋ฅผ state๋กœ์„œ ๋ฐฐ์—ด๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  items์˜ ๊ฐฏ์ˆ˜๋ฅผ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์„ ๋•Œ, items์˜ ๊ฐฏ์ˆ˜๋ฅผ ๋˜๋‹ค๋ฅธ state๋กœ ๋‘์ง€ ๋ง์•„๋ผ. ๊ทธ์ € items์˜ ๊ธธ์ด๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ๋ณด์—ฌ์ฃผ๋ฉด ๋œ๋‹ค.

์ตœ์†Œํ•œ์˜ state๋กœ ์ตœ๋Œ€ํ•œ์˜ UI๋ฅผ ํ‘œํ˜„ํ•˜๋ผ๋Š” ๋ง ๊ฐ™๋‹ค. state๊ฐ€ ์“ธ๋ฐ์—†์ด ๋งŽ์•„์ ธ๋ด์•ผ ๋ณต์žกํ•ด์ง€๊ณ  ๋ถˆํ•„์š”ํ•œ ๊ณ„์‚ฐ์ด ๋งŽ์•„์ง€๊ธฐ ๋•Œ๋ฌธ์ผ ๊ฒƒ์ด๋‹ค.

์•„๋ž˜ 4๊ฐœ์˜ ์˜ˆ์‹œ๋ฅผ ๋‘๊ณ  state์ธ์ง€ ์•„๋‹Œ์ง€ ํŒ๋‹จํ•ด๋ณด์ž.

  1. The search text the user has entered
  2. The original list of products
  3. The value of the checkbox
  4. The filtered list of products

์–ด๋Š๊ฒŒ state์ผ๊นŒ? state๊ฐ€ ์•„๋‹Œ ๊ฒƒ์„ ๋จผ์ € ์ฐพ์•„๋ณด์ž.

  • ์‹œ๊ฐ„์ด ํ˜๋Ÿฌ๋„ ๋ณ€ํ•˜์ง€ ์•Š๋Š”๊ฐ€? ๊ทธ๋ ‡๋‹ค๋ฉด state๊ฐ€ ์•„๋‹ˆ๋‹ค.
  • ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ props๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋˜๋Š”๊ฐ€? ๊ทธ๋ ‡๋‹ค๋ฉด state๊ฐ€ ์•„๋‹ˆ๋‹ค.
    • (Q). ๋ถ€๋ชจ์˜ state๊ฐ€ ์ž์‹์—๊ฒŒ props๋กœ ์ „๋‹ฌ๋˜๋Š” ๊ฒฝ์šฐ๋Š”? (A). ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ณธ์ธ์˜ state๊ฐ€ ์•„๋‹ˆ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์กด์žฌํ•˜๋Š” state๋‚˜ props๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ณ„์‚ฐ๋  ์ˆ˜ ์žˆ๋Š”๊ฐ€? ๊ทธ๋ ‡๋‹ค๋ฉด state๊ฐ€ ์•„๋‹ˆ๋‹ค.

๋‹ค์‹œ ์œ„์˜ ์˜ˆ์‹œ๋ฅผ ๋ณด์ž.

  1. The search text the user has entered: ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ•˜๊ณ  ๋‹ค๋ฅธ ๊ฒƒ์œผ๋กœ๋ถ€ํ„ฐ ๊ณ„์‚ฐ๋  ์ˆ˜ ์—†๋‹ค. state์ด๋‹ค.
  2. The original list of products: ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ props๋กœ ์ „๋‹ฌ๋˜์—ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฌ๋‹ค. state๊ฐ€ ์•„๋‹ˆ๋‹ค.
  3. The value of the checkbox: ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ•˜๊ณ  ๋‹ค๋ฅธ ๊ฒƒ์œผ๋กœ๋ถ€ํ„ฐ ๊ณ„์‚ฐ๋  ์ˆ˜ ์—†๋‹ค. state์ด๋‹ค.
  4. The filtered list of products: ๋‹ค๋ฅธ original list๋ฅผ ๊ฐ€์ ธ์™€์„œ ์–ด๋–ค search text์— ๋”ฐ๋ผ filtering๋œ ๊ฒƒ์ด๋ผ๋ฉด state๊ฐ€ ์•„๋‹ˆ๋‹ค.

Props vs State
Props: ํ•จ์ˆ˜๋ฅผ ํ†ต๊ณผํ•˜๋Š” ์ธ์ž์™€ ๊ฐ™๋‹ค. ๋ถ€๋ชจ์—์„œ ์ž์‹์œผ๋กœ ์ „๋‹ฌ๋˜๋ฉฐ ๊ทธ ๋ชจ์–‘(์™ธ๊ด€)์„ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.
State: ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •๋ณด๋ฅผ ์ถ”์ ํ•˜๊ณ  ์ƒํ˜ธ ์ž‘์šฉ์— ๋”ฐ๋ผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

Props์˜ค State๋Š” ๋‹ค๋ฅด์ง€๋งŒ ํ•จ๊ป˜ ์ž‘๋™ํ•œ๋‹ค. ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ช‡๋ช‡ ์ •๋ณด๋ฅผ state๋กœ ๋‘๊ณ  ์ด๋ฅผ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ props๋กœ ์ „๋‹ฌํ•œ๋‹ค.

Step 4: Identify where your state should live

์ด์ œ ๋‹น์‹ ์€ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ state๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•˜๋Š”์ง€ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค. ๋‹ค์Œ์œผ๋กœ๋Š” ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ทธ state์— ๋Œ€ํ•œ ์ฑ…์ž„์ด ์žˆ๊ณ  ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•˜๋Š”์ง€ ์•Œ์•„์•ผ ํ•œ๋‹ค. ๊ธฐ์–ตํ•˜์ž: ๋ฆฌ์•กํŠธ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์œ„์—์„œ ํ•˜์œ„๋กœ ์ „๋‹ฌ๋˜๋Š” one-way data flow`๋ฅผ ๋”ฐ๋ฅธ๋‹ค. ์ง€๊ธˆ ๋‹น์žฅ์€ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•ด๋‹น state์— ๋Œ€ํ•œ ์ฑ…์ž„์ด ์žˆ๋Š”์ง€ ๋ชจํ˜ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๋‹จ๊ณ„๋กœ ์ƒ๊ฐํ•ด๋ณด์ž.

  1. ๊ทธ state๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์•„๋ผ.
  2. ๊ทธ๋“ค์˜ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์•„๋ผ.
  3. ๊ทธ state๊ฐ€ ์–ด๋””์— ์žˆ์–ด์•ผ๋˜๋Š”์ง€ ๊ฒฐ์ •ํ•ด๋ผ.
    • ์ข…์ข… ๋‹น์‹ ์€ ๊ทธ state๋ฅผ ๊ทธ๋“ค์˜ ๊ณตํ†ต๋œ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ๋‘˜ ์ˆ˜ ์žˆ๋‹ค.
    • ๋˜ํ•œ ๊ทธ๋“ค์˜ ๊ณตํ†ต๋œ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ์œ„์— ๋‘˜ ์ˆ˜๋„ ์žˆ๋‹ค.(props๋กœ ์ „๋‹ฌ๋งŒ ๋˜๋ฉด ๋˜๋‹ˆ๊นŒ)
    • ๊ทธ state๋ฅผ ์–ด๋””์— ๋‘ฌ์•ผํ• ์ง€ ์ฐพ์ง€ ๋ชปํ•˜๊ฒ ๋‹ค๋ฉด, ๊ทธ state๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ผ. ๊ทธ๋ฆฌ๊ณ  ๊ทธ state๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ props๋กœ ์ „๋‹ฌํ•˜๋ผ.

๋‹ค์‹œ ์œ„์˜ ์˜ˆ์ œ๋กœ ๋Œ์•„์™€์„œ ์œ„์˜ ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•ด๋ณด์ž.

  1. search text์™€ checkbox value๋ฅผ state๋กœ ๋‘˜ ์ˆ˜ ์žˆ๋‹ค.

    • ProductTable: state(search text์™€ checkbox value)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ products๋ฅผ filterํ•ด์•ผ ํ•œ๋‹ค.
    • SearchBar: state(search text์™€ checkbox value)๋ฅผ ui๋กœ ๋ณด์—ฌ์ค˜์•ผ ํ•œ๋‹ค.
  2. ์œ„ ๋‘˜์˜ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ฒซ๋ฒˆ์งธ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ: FilterableProductTable

  3. ๋”ฐ๋ผ์„œ state๋Š” FilterableProductTable์— ๋‘˜ ์ˆ˜ ์žˆ๋‹ค.

code-sandbox ์ฐธ๊ณ 

๊ทธ๋Ÿฌ๋‚˜ ์•„๋ž˜์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field.

SearchBar์˜ input ํƒœ๊ทธ์˜ value๋กœ filterText๋ฅผ ์ „๋‹ฌํ–ˆ์ง€๋งŒ, ์œ ์ €์— ์˜ํ•ด input ๊ฐ’์ด ๋ณ€ํ•  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น value๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ด๋‹ค.

Step 5: Add inverse data flow

์ „์ฒด์ ์œผ๋กœ ์•ฑ์€ props์™€ state๋ฅผ ๊ณ„์ธต์— ๋”ฐ๋ผ ์•„๋ž˜๋กœ ์ž˜ ์ „๋‹ฌํ•˜์—ฌ ๋ Œ๋”๋ง๋˜์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ user input์— ๋”ฐ๋ฅธ state ๋ณ€ํ™”๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ data flow๋ฅผ ์ง€์›ํ•ด์•ผํ•œ๋‹ค. ๊ณ„์ธต์˜ ๊ฑฐ์˜ ๋งจ ์•„๋ž˜์— ์žˆ๋Š” form ์ปดํฌ๋„ŒํŠธ์—์„œ FilterableProductTable์˜ state๋ฅผ ๋ณ€๊ฒฝํ•ด์•ผํ•œ๋‹ค.

๋ฆฌ์•กํŠธ๋Š” ์ด data flow๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋งŒ๋“ค์—ˆ์ง€๋งŒ, ์ด๋Š” ์–‘๋ฐฉํ–ฅ๋ณด๋‹จ ์•ฝ๊ฐ„์˜ ๋” ๋งŽ์€ ํƒ€์ดํ•‘์„ ์š”๊ตฌํ•œ๋‹ค. ๋‹น์‹ ์ด input์— ํƒ€์ดํ•‘ํ•˜๊ฑฐ๋‚˜ checkbox๋ฅผ ํด๋ฆญํ•ด๋„ ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค.(๋ฆฌ์•กํŠธ๊ฐ€ ๋‹น์‹ ์˜ input์„ ๋ฌด์‹œํ•œ๋‹ค.) ๋‹น์‹ ์ด <input value={filterText} />๋ผ๊ณ  ์ผ๊ธฐ ๋•Œ๋ฌธ์—, filterText๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š๋Š”ํ•œ input์˜ value ๋˜ํ•œ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ stockOnly๋„ ๊ทธ๋ ‡๋‹ค. ์ด 2๊ฐœ์˜ state๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๊ฐ๊ฐ์˜ setState๋ฟ์ด๋‹ค.

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);
  
  const handleFilterTextChange = (e) => {
    setFilterText(e.target.value);
  };
  
  const handleInStockOnly = () => {
    setInStockOnly(!inStockOnly);
  };

  return (
    <div>
      <SearchBar 
        filterText={filterText} 
        inStockOnly={inStockOnly}
        onFilterTextChange={handleFilterTextChange}
        onInStockOnlyChange={handleInStockOnly} />
// ์ƒ๋žต

code-sandbox ์ฐธ๊ณ 

์œ„์˜ ์ฐธ๊ณ  ์ฝ”๋“œ์—์„œ๋Š” handle ํ•จ์ˆ˜๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด๋‘์ง€ ์•Š์•˜์ง€๋งŒ, ๊ฐ€๋Šฅํ•˜๋ฉด ๋”ฐ๋กœ ๋งŒ๋“ค์–ด๋‘๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.