๐Ÿ“˜ React Props์™€ State ์™„์ „ ์ •๋ณต: ์ปดํฌ๋„ŒํŠธ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๊ฐ€์ด๋“œ

@leekh8 ยท May 02, 2023 ยท 17 min read

React ๊ธฐ์ดˆ ์ •๋ฆฌ โ€” Props์™€ State ์™„์ „ ์ •๋ณต

React๋ฅผ ์ฒ˜์Œ ๋ฐฐ์šธ ๋•Œ ๊ฐ€์žฅ ๋จผ์ € ๋งˆ์ฃผ์น˜๋Š” ๋‘ ๊ฐœ๋…์ด ๋ฐ”๋กœ Props์™€ State๋‹ค. ์ด ๋‘ ๊ฐ€์ง€๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•ด์•ผ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ๊ธฐ์ดˆ๋ถ€ํ„ฐ ์‹ฌํ™”๊นŒ์ง€, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ „์—์„œ ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜๊นŒ์ง€ ๋ชจ๋‘ ๋‹ค๋ฃฌ๋‹ค.


1. Props๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

Props๋Š” Properties์˜ ์ค„์ž„๋ง๋กœ, ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด๋‹ค. ํ•จ์ˆ˜์˜ ์ธ์ž(argument)์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•œ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

// ์ž์‹ ์ปดํฌ๋„ŒํŠธ
const Welcome = (props) => {
  return <h1>Hello, {props.name}!</h1>
}

// ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ
const App = () => {
  return (
    <div>
      <Welcome name="Amy" />
      <Welcome name="Andy" />
      <Welcome name="Sapiens" />
    </div>
  )
}

์œ„ ์˜ˆ์ œ์—์„œ name์ด๋ผ๋Š” props๋ฅผ ์ž์‹ ์ปดํฌ๋„ŒํŠธ Welcome์— ์ „๋‹ฌํ•˜๊ณ  ์žˆ๋‹ค. JSX์—์„œ HTML ์†์„ฑ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜๋ฉด ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ props ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น์œผ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์“ฐ๊ธฐ

๋งค๋ฒˆ props.name์ฒ˜๋Ÿผ ์“ฐ๋Š” ๋Œ€์‹ , ES6 ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น์„ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๊น”๋”ํ•˜๋‹ค.

// ๋ฐฉ๋ฒ• 1: ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ๋ฐ”๋กœ ๊ตฌ์กฐ๋ถ„ํ•ด
const Welcome = ({ name, age, city }) => {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Age: {age}</p>
      <p>City: {city}</p>
    </div>
  )
}

// ๋ฐฉ๋ฒ• 2: ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ๊ตฌ์กฐ๋ถ„ํ•ด
const Welcome = (props) => {
  const { name, age, city } = props
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Age: {age}</p>
      <p>City: {city}</p>
    </div>
  )
}

2. Props ์‹ฌํ™”: defaultProps๋กœ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •ํ•˜๊ธฐ

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํŠน์ • props๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š์•˜์„ ๋•Œ ๊ธฐ๋ณธ๊ฐ’์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ defaultProps๋ผ๊ณ  ํ•œ๋‹ค.

const Button = ({ label, color, size }) => {
  return (
    <button
      style={{
        backgroundColor: color,
        fontSize: size === 'large' ? '18px' : '14px',
        padding: '8px 16px',
      }}
    >
      {label}
    </button>
  )
}

// defaultProps ์„ค์ •
Button.defaultProps = {
  label: 'ํด๋ฆญ',
  color: '#4CAF50',
  size: 'medium',
}

// ์‚ฌ์šฉ: label๋งŒ ์ „๋‹ฌํ•ด๋„ ๋‚˜๋จธ์ง€๋Š” ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ
const App = () => {
  return (
    <div>
      <Button label="์ €์žฅ" color="#2196F3" />
      <Button label="์‚ญ์ œ" color="#f44336" size="large" />
      <Button /> {/* ๋ชจ๋“  ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ */}
    </div>
  )
}

์ฐธ๊ณ : React ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” defaultProps ๋Œ€์‹  ES6 ๊ธฐ๋ณธ๊ฐ’ ๋ฌธ๋ฒ•์„ ๋” ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

// defaultProps ๋Œ€์‹  ๊ธฐ๋ณธ๊ฐ’ ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉ (๋” ํ˜„๋Œ€์ ์ธ ๋ฐฉ์‹)
const Button = ({ label = 'ํด๋ฆญ', color = '#4CAF50', size = 'medium' }) => {
  return (
    <button style={{ backgroundColor: color }}>
      {label}
    </button>
  )
}

3. PropTypes๋กœ ํƒ€์ž… ๊ฒ€์‚ฌํ•˜๊ธฐ

JavaScript๋Š” ๋™์  ํƒ€์ž… ์–ธ์–ด๋ผ ์ž˜๋ชป๋œ ํƒ€์ž…์˜ props๋ฅผ ์ „๋‹ฌํ•ด๋„ ๋ฐ”๋กœ ์—๋Ÿฌ๊ฐ€ ๋‚˜์ง€ ์•Š๋Š”๋‹ค. prop-types ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

npm install prop-types
import PropTypes from 'prop-types'

const UserCard = ({ name, age, isAdmin, hobbies, onClick }) => {
  return (
    <div className="user-card">
      <h2>{name} {isAdmin && '(๊ด€๋ฆฌ์ž)'}</h2>
      <p>๋‚˜์ด: {age}์„ธ</p>
      <ul>
        {hobbies.map((hobby, index) => (
          <li key={index}>{hobby}</li>
        ))}
      </ul>
      <button onClick={onClick}>ํ”„๋กœํ•„ ๋ณด๊ธฐ</button>
    </div>
  )
}

// PropTypes ์ •์˜
UserCard.propTypes = {
  name: PropTypes.string.isRequired,        // ๋ฌธ์ž์—ด, ํ•„์ˆ˜
  age: PropTypes.number.isRequired,          // ์ˆซ์ž, ํ•„์ˆ˜
  isAdmin: PropTypes.bool,                   // ๋ถˆ๋ฆฌ์–ธ, ์„ ํƒ
  hobbies: PropTypes.arrayOf(PropTypes.string), // ๋ฌธ์ž์—ด ๋ฐฐ์—ด
  onClick: PropTypes.func,                   // ํ•จ์ˆ˜
}

UserCard.defaultProps = {
  isAdmin: false,
  hobbies: [],
  onClick: () => {},
}

PropTypes๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ฃผ์š” ํƒ€์ž… ๊ฒ€์‚ฌ๊ธฐ:

ํƒ€์ž… ๊ฒ€์‚ฌ๊ธฐ ์„ค๋ช…
PropTypes.string ๋ฌธ์ž์—ด
PropTypes.number ์ˆซ์ž
PropTypes.bool ๋ถˆ๋ฆฌ์–ธ
PropTypes.array ๋ฐฐ์—ด
PropTypes.object ๊ฐ์ฒด
PropTypes.func ํ•จ์ˆ˜
PropTypes.node ๋ Œ๋”๋ง ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๊ฒƒ
PropTypes.element React ์—˜๋ฆฌ๋จผํŠธ
PropTypes.arrayOf(type) ํŠน์ • ํƒ€์ž…์˜ ๋ฐฐ์—ด
PropTypes.shape({}) ํŠน์ • ๊ตฌ์กฐ์˜ ๊ฐ์ฒด
.isRequired ํ•„์ˆ˜ props ์ง€์ •

TypeScript๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด? TypeScript๋ฅผ ์“ธ ๊ฒฝ์šฐ PropTypes ์—†์ด๋„ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๋‹ค. ์ตœ์‹  ํ”„๋กœ์ ํŠธ์—์„œ๋Š” TypeScript + React๋ฅผ ์กฐํ•ฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.


4. children prop: ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ์ปดํฌ๋„ŒํŠธ ๋„ฃ๊ธฐ

React์—๋Š” ํŠน๋ณ„ํ•œ props๊ฐ€ ์žˆ๋Š”๋ฐ, ๋ฐ”๋กœ children์ด๋‹ค. JSX ํƒœ๊ทธ ์‚ฌ์ด์— ๋„ฃ์€ ๋‚ด์šฉ์ด children์œผ๋กœ ์ „๋‹ฌ๋œ๋‹ค.

// children์„ ๋ฐ›๋Š” ์ปดํฌ๋„ŒํŠธ (๋ž˜ํผ ์ปดํฌ๋„ŒํŠธ)
const Card = ({ title, children, className }) => {
  return (
    <div className={`card ${className}`}>
      <div className="card-header">
        <h3>{title}</h3>
      </div>
      <div className="card-body">
        {children}  {/* ์—ฌ๊ธฐ์— ์ž์‹ ์š”์†Œ๋“ค์ด ๋ Œ๋”๋ง๋จ */}
      </div>
    </div>
  )
}

// ์‚ฌ์šฉ
const App = () => {
  return (
    <div>
      <Card title="์‚ฌ์šฉ์ž ์ •๋ณด" className="user-card">
        <p>์ด๋ฆ„: Amy</p>
        <p>์ด๋ฉ”์ผ: amy@example.com</p>
        <button>์ˆ˜์ •</button>
      </Card>

      <Card title="๊ณต์ง€์‚ฌํ•ญ">
        <ul>
          <li>์‹œ์Šคํ…œ ์ ๊ฒ€ ์•ˆ๋‚ด</li>
          <li>์ƒˆ ๊ธฐ๋Šฅ ์—…๋ฐ์ดํŠธ</li>
        </ul>
      </Card>
    </div>
  )
}

children์„ ํ™œ์šฉํ•˜๋ฉด ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ, ๋ชจ๋‹ฌ, ์นด๋“œ ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ ๋งค์šฐ ์œ ์šฉํ•˜๋‹ค.


5. Spread Props: props ํ•œ๊บผ๋ฒˆ์— ์ „๋‹ฌํ•˜๊ธฐ

์—ฌ๋Ÿฌ props๋ฅผ ํ•œ ๋ฒˆ์— ์ „๋‹ฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

const Input = ({ label, id, ...rest }) => {
  // label, id๋Š” ๋”ฐ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ๋‚˜๋จธ์ง€ ์†์„ฑ๋“ค์€ input ์—˜๋ฆฌ๋จผํŠธ์— ์ „๋‹ฌ
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} {...rest} />
    </div>
  )
}

const App = () => {
  const inputProps = {
    type: 'email',
    placeholder: '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”',
    required: true,
    autoComplete: 'email',
  }

  return (
    <Input
      label="์ด๋ฉ”์ผ"
      id="email"
      {...inputProps}  // ์Šคํ”„๋ ˆ๋“œ๋กœ ํ•œ๊บผ๋ฒˆ์— ์ „๋‹ฌ
    />
  )
}

๋‹ค๋งŒ ์Šคํ”„๋ ˆ๋“œ props๋Š” ๋‚จ์šฉํ•˜๋ฉด ์–ด๋–ค props๊ฐ€ ์ „๋‹ฌ๋˜๋Š”์ง€ ์ถ”์ ํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง€๋ฏ€๋กœ ์‹ ์ค‘ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.


6. Props Drilling ๋ฌธ์ œ

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊นŠ์ด ์ค‘์ฒฉ๋ ์ˆ˜๋ก ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด ์ค‘๊ฐ„ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์‚ฌ์šฉํ•˜์ง€๋„ ์•Š๋Š” props๋ฅผ ๊ณ„์† ๋‚ด๋ ค์ค˜์•ผ ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค. ์ด๋ฅผ Props Drilling์ด๋ผ๊ณ  ํ•œ๋‹ค.

// Props Drilling ์˜ˆ์‹œ: theme์„ ๊ณ„์† ๋‚ด๋ ค์ค˜์•ผ ํ•จ
const App = () => {
  const theme = 'dark'
  return <Page theme={theme} />
}

const Page = ({ theme }) => {
  return <Sidebar theme={theme} />
}

const Sidebar = ({ theme }) => {
  return <Menu theme={theme} />
}

const Menu = ({ theme }) => {
  return <MenuItem theme={theme} />
}

const MenuItem = ({ theme }) => {
  // ๋“œ๋””์–ด ์‹ค์ œ๋กœ theme์„ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ
  return (
    <li style={{ color: theme === 'dark' ? 'white' : 'black' }}>
      ๋ฉ”๋‰ด ํ•ญ๋ชฉ
    </li>
  )
}

Page, Sidebar, Menu๋Š” theme์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ์ „๋‹ฌ๋งŒ ํ•œ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ 5๋‹จ๊ณ„, 10๋‹จ๊ณ„๋กœ ๊นŠ์–ด์ง€๋ฉด ๊ด€๋ฆฌ๊ฐ€ ๋งค์šฐ ์–ด๋ ค์›Œ์ง„๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:

  • Context API: React ๋‚ด์žฅ ์ „์—ญ ์ƒํƒœ ๊ณต์œ  (๋‹ค์Œ ๊ธ€์—์„œ ๋‹ค๋ฃธ)
  • ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: Redux, Zustand, Jotai ๋“ฑ
  • ์ปดํฌ๋„ŒํŠธ ํ•ฉ์„ฑ(Composition): children์ด๋‚˜ render props ํŒจํ„ด ํ™œ์šฉ

7. State๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

State๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ๊ด€๋ฆฌ๋˜๋Š” ๋™์  ๋ฐ์ดํ„ฐ๋‹ค. ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ, API ์‘๋‹ต, ์‹œ๊ฐ„ ๊ฒฝ๊ณผ ๋“ฑ์œผ๋กœ ์ธํ•ด ๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์„ State๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.

Props๊ฐ€ "์™ธ๋ถ€์—์„œ ์ฃผ์–ด์ง€๋Š” ๊ฐ’"์ด๋ผ๋ฉด, State๋Š” "์ปดํฌ๋„ŒํŠธ ์Šค์Šค๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ’"์ด๋‹ค.

import { useState } from 'react'

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

  return (
    <div>
      <p>ํ˜„์žฌ ์นด์šดํŠธ: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>์ดˆ๊ธฐํ™”</button>
    </div>
  )
}

useState๋Š” ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, ์ฒซ ๋ฒˆ์งธ ์š”์†Œ๋Š” ํ˜„์žฌ State ๊ฐ’, ๋‘ ๋ฒˆ์งธ ์š”์†Œ๋Š” State๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋‹ค.


8. State ์‹ฌํ™”: ๋น„๋™๊ธฐ ์—…๋ฐ์ดํŠธ ์ฃผ์˜์ 

State ์—…๋ฐ์ดํŠธ๋Š” ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค. React๋Š” ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ์—ฌ๋Ÿฌ State ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฌถ์–ด์„œ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌ(๋ฐฐ์น˜ ์ฒ˜๋ฆฌ)ํ•œ๋‹ค.

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

  const handleBadClick = () => {
    // ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด 3์ด ์•„๋‹ˆ๋ผ 1๋งŒ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค!
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)
    // ์„ธ ๋ฒˆ ๋ชจ๋‘ ๊ฐ™์€ 'count' ๊ฐ’์„ ์ฐธ์กฐํ•˜๊ธฐ ๋•Œ๋ฌธ
  }

  const handleGoodClick = () => {
    // ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ์‹  state๋ฅผ ๋ณด์žฅ
    setCount(prev => prev + 1)
    setCount(prev => prev + 1)
    setCount(prev => prev + 1)
    // ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ •ํ™•ํžˆ 3์ด ์ฆ๊ฐ€
  }

  return (
    <div>
      <p>์นด์šดํŠธ: {count}</p>
      <button onClick={handleBadClick}>์ž˜๋ชป๋œ ๋ฐฉ๋ฒ• (+3 ์‹œ๋„)</button>
      <button onClick={handleGoodClick}>์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ• (+3)</button>
    </div>
  )
}

ํ•ต์‹ฌ ์›์น™: ์ด์ „ State๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒˆ State๋ฅผ ๊ณ„์‚ฐํ•  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ (prev => prev + 1)๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.


9. ์—ฌ๋Ÿฌ State ๊ด€๋ฆฌํ•˜๊ธฐ

ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ์—ฌ๋Ÿฌ State๋ฅผ ๊ด€๋ฆฌํ•  ๋•Œ๋Š” ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ๋ฒ•์ด ์žˆ๋‹ค.

๋ฐฉ๋ฒ• 1: ์—ฌ๋Ÿฌ useState ์‚ฌ์šฉ (๋‹จ์ˆœํ•œ ๊ฐ’์— ์ ํ•ฉ)

function UserForm() {
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [age, setAge] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)

  const handleSubmit = async (e) => {
    e.preventDefault()
    setIsSubmitting(true)
    // API ํ˜ธ์ถœ...
    setIsSubmitting(false)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="์ด๋ฆ„" />
      <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="์ด๋ฉ”์ผ" />
      <input value={age} onChange={(e) => setAge(e.target.value)} placeholder="๋‚˜์ด" />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '์ „์†ก ์ค‘...' : '์ œ์ถœ'}
      </button>
    </form>
  )
}

๋ฐฉ๋ฒ• 2: ๊ฐ์ฒด State ์‚ฌ์šฉ (๊ด€๋ จ ๋ฐ์ดํ„ฐ ๋ฌถ๊ธฐ)

function UserForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: '',
  })
  const [isSubmitting, setIsSubmitting] = useState(false)

  // ๋ฒ”์šฉ ํ•ธ๋“ค๋Ÿฌ: input name ์†์„ฑ์„ ์ด์šฉ
  const handleChange = (e) => {
    const { name, value } = e.target
    setFormData(prev => ({
      ...prev,      // ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ (๋ถˆ๋ณ€์„ฑ ์œ ์ง€!)
      [name]: value // ํ•ด๋‹น ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธ
    }))
  }

  return (
    <form>
      <input name="name" value={formData.name} onChange={handleChange} placeholder="์ด๋ฆ„" />
      <input name="email" value={formData.email} onChange={handleChange} placeholder="์ด๋ฉ”์ผ" />
      <input name="age" value={formData.age} onChange={handleChange} placeholder="๋‚˜์ด" />
    </form>
  )
}

10. ๋ฐฐ์—ด State ๋‹ค๋ฃจ๊ธฐ

๋ฐฐ์—ด์„ State๋กœ ๊ด€๋ฆฌํ•  ๋•Œ๋„ ๋ถˆ๋ณ€์„ฑ์„ ์ง€์ผœ์•ผ ํ•œ๋‹ค. ๋ฐฐ์—ด์„ ์ง์ ‘ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฉ”์„œ๋“œ(push, pop, splice ๋“ฑ)๋Š” ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ , ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'React ๊ณต๋ถ€ํ•˜๊ธฐ', done: false },
    { id: 2, text: '์šด๋™ํ•˜๊ธฐ', done: true },
  ])
  const [inputText, setInputText] = useState('')

  // ํ•ญ๋ชฉ ์ถ”๊ฐ€
  const addTodo = () => {
    if (!inputText.trim()) return
    const newTodo = {
      id: Date.now(),
      text: inputText,
      done: false,
    }
    setTodos(prev => [...prev, newTodo])  // ์ƒˆ ๋ฐฐ์—ด ์ƒ์„ฑ
    setInputText('')
  }

  // ์™„๋ฃŒ ํ† ๊ธ€
  const toggleTodo = (id) => {
    setTodos(prev =>
      prev.map(todo =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    )
  }

  // ํ•ญ๋ชฉ ์‚ญ์ œ
  const deleteTodo = (id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id))
  }

  return (
    <div>
      <div>
        <input
          value={inputText}
          onChange={(e) => setInputText(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && addTodo()}
          placeholder="ํ•  ์ผ ์ž…๋ ฅ..."
        />
        <button onClick={addTodo}>์ถ”๊ฐ€</button>
      </div>
      <ul>
        {todos.map(todo => (
          <li key={todo.id} style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
            <input
              type="checkbox"
              checked={todo.done}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.text}
            <button onClick={() => deleteTodo(todo.id)}>์‚ญ์ œ</button>
          </li>
        ))}
      </ul>
      <p>์ด {todos.length}๊ฐœ / ์™„๋ฃŒ {todos.filter(t => t.done).length}๊ฐœ</p>
    </div>
  )
}

๋ฐฐ์—ด ์กฐ์ž‘ ์‹œ ๋ถˆ๋ณ€์„ฑ์„ ์ง€ํ‚ค๋Š” ํŒจํ„ด ์ •๋ฆฌ:

์ž‘์—… ์ž˜๋ชป๋œ ๋ฐฉ๋ฒ• (์ง์ ‘ ๋ณ€๊ฒฝ) ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ• (์ƒˆ ๋ฐฐ์—ด)
์ถ”๊ฐ€ arr.push(item) [...arr, item]
์•ž์— ์ถ”๊ฐ€ arr.unshift(item) [item, ...arr]
์‚ญ์ œ arr.splice(idx, 1) arr.filter((_, i) => i !== idx)
์ˆ˜์ • arr[idx] = newItem arr.map((item, i) => i === idx ? newItem : item)
์ •๋ ฌ arr.sort() [...arr].sort()

11. ๋ถˆ๋ณ€์„ฑ(Immutability)์ด๋ž€? ์™œ ์ค‘์š”ํ•œ๊ฐ€?

๋ถˆ๋ณ€์„ฑ์€ "ํ•œ ๋ฒˆ ๋งŒ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค"๋Š” ์›์น™์ด๋‹ค. React์—์„œ ๋ถˆ๋ณ€์„ฑ์ด ์ค‘์š”ํ•œ ์ด์œ ๋Š” React๊ฐ€ State ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๋Š” ๋ฐฉ์‹ ๋•Œ๋ฌธ์ด๋‹ค.

React์˜ ๋ณ€๊ฒฝ ๊ฐ์ง€ ๋ฐฉ์‹

React๋Š” State๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด **์–•์€ ๋น„๊ต(Shallow Comparison)**๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ฆ‰, ๊ฐ์ฒด์˜ ๋‚ด์šฉ์ด ๋ฐ”๋€Œ์—ˆ๋Š”์ง€ ๊นŠ์ด ๋น„๊ตํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ฐธ์กฐ(Reference)๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•œ๋‹ค.

// ์ž˜๋ชป๋œ ์˜ˆ: ์ฐธ์กฐ๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์•„ React๊ฐ€ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ ๋ชปํ•จ
const [user, setUser] = useState({ name: 'Amy', age: 30 })

const badUpdate = () => {
  user.age = 31  // ์ง์ ‘ ์ˆ˜์ •
  setUser(user)  // ๊ฐ™์€ ์ฐธ์กฐ์ด๋ฏ€๋กœ React๊ฐ€ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ ๋ชปํ•จ!
}

// ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ: ์ƒˆ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ฐธ์กฐ ๋ณ€๊ฒฝ
const goodUpdate = () => {
  setUser(prev => ({
    ...prev,  // ๊ธฐ์กด ๊ฐ์ฒด์˜ ์†์„ฑ์„ ๋ณต์‚ฌ
    age: 31   // ๋ณ€๊ฒฝํ•  ์†์„ฑ๋งŒ ๋ฎ์–ด์”€
  }))
}

์ค‘์ฒฉ ๊ฐ์ฒด์—์„œ์˜ ๋ถˆ๋ณ€์„ฑ

์ค‘์ฒฉ ๊ฐ์ฒด์—์„œ๋Š” ์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž๊ฐ€ ์–•์€ ๋ณต์‚ฌ๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

const [state, setState] = useState({
  user: {
    name: 'Amy',
    address: {
      city: '์„œ์šธ',
      district: '๊ฐ•๋‚จ๊ตฌ',
    }
  }
})

// ์ค‘์ฒฉ๋œ ๊ฐ์ฒด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธ
const updateCity = (newCity) => {
  setState(prev => ({
    ...prev,
    user: {
      ...prev.user,
      address: {
        ...prev.user.address,
        city: newCity,
      }
    }
  }))
}

๊นŠ์ด ์ค‘์ฒฉ๋œ ๊ฐ์ฒด๋ฅผ ๋‹ค๋ฃฐ์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง„๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ๋‹ค.

Immer ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: ๋ถˆ๋ณ€์„ฑ์„ ์‰ฝ๊ฒŒ

immer๋Š” ๋ถˆ๋ณ€์„ฑ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค. ๋งˆ์น˜ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋„, ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ์ƒˆ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์ค€๋‹ค.

npm install immer
import { useState } from 'react'
import { produce } from 'immer'

const [state, setState] = useState({
  user: {
    name: 'Amy',
    address: {
      city: '์„œ์šธ',
      district: '๊ฐ•๋‚จ๊ตฌ',
    }
  },
  todos: [
    { id: 1, text: '๊ณต๋ถ€', done: false }
  ]
})

// immer ์‚ฌ์šฉ: ์ง์ ‘ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ฝ”๋“œ ์ž‘์„ฑ
const updateCity = (newCity) => {
  setState(produce(draft => {
    draft.user.address.city = newCity  // ์ง์ ‘ ์ˆ˜์ •์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ์•ˆ์ „
  }))
}

const toggleTodo = (id) => {
  setState(produce(draft => {
    const todo = draft.todos.find(t => t.id === id)
    if (todo) todo.done = !todo.done  // ์ง์ ‘ ์ˆ˜์ •
  }))
}

immer๋Š” ํŠนํžˆ ์ค‘์ฒฉ์ด ๊นŠ์€ State๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚จ๋‹ค.


12. ๋ฆฌ๋ Œ๋”๋ง: ์–ธ์ œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜๋‚˜?

React ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์Œ ์„ธ ๊ฐ€์ง€ ์ƒํ™ฉ์—์„œ ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค:

  1. ์ž์‹ ์˜ State๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ
  2. ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ๋ฐ›๋Š” Props๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ
  3. ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋  ๋•Œ (props๊ฐ€ ๋ฐ”๋€Œ์ง€ ์•Š์•„๋„!)
const Parent = () => {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('Amy')

  console.log('Parent ๋ Œ๋”๋ง')

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>์นด์šดํŠธ ์ฆ๊ฐ€</button>
      <Child name={name} />  {/* count๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ Child๋Š” ๋ฆฌ๋ Œ๋”๋ง๋จ! */}
    </div>
  )
}

const Child = ({ name }) => {
  console.log('Child ๋ Œ๋”๋ง')
  return <p>์ด๋ฆ„: {name}</p>
}

๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด count๊ฐ€ ๋ณ€ํ•ด์„œ Parent๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋˜๊ณ , Child๋„ ๋”ฐ๋ผ์„œ ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค. name์€ ๋ฐ”๋€Œ์ง€ ์•Š์•˜๋Š”๋ฐ๋„ ๋ง์ด๋‹ค.


13. React.memo๋กœ ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง ๋ฐฉ์ง€

React.memo๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•˜๋Š” ๊ณ ์ฐจ ์ปดํฌ๋„ŒํŠธ(HOC)๋‹ค. props๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉด ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ด๋‹ค.

import { useState, memo } from 'react'

const Parent = () => {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('Amy')

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>์นด์šดํŠธ: {count}</button>
      <button onClick={() => setName('Andy')}>์ด๋ฆ„ ๋ณ€๊ฒฝ</button>
      <MemoizedChild name={name} />
    </div>
  )
}

// React.memo๋กœ ๊ฐ์‹ธ๊ธฐ
const MemoizedChild = memo(({ name }) => {
  console.log('Child ๋ Œ๋”๋ง')
  return <p>์ด๋ฆ„: {name}</p>
})

// ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ ์–ธํ•  ๋•Œ ๋ฐ”๋กœ ๊ฐ์‹ธ๊ธฐ
const Child = memo(({ name }) => {
  return <p>์ด๋ฆ„: {name}</p>
})

์ด์ œ count๊ฐ€ ๋ณ€ํ•ด๋„ Child๋Š” ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š๋Š”๋‹ค. name์ด ๋ฐ”๋€” ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค.

React.memo์˜ ์ปค์Šคํ…€ ๋น„๊ต ํ•จ์ˆ˜

๊ธฐ๋ณธ์ ์œผ๋กœ React.memo๋Š” ์–•์€ ๋น„๊ต๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ปค์Šคํ…€ ๋น„๊ต ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

const MyComponent = memo(
  ({ data }) => {
    return <div>{data.value}</div>
  },
  (prevProps, nextProps) => {
    // true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง ์•ˆ ํ•จ
    // false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง ํ•จ
    return prevProps.data.id === nextProps.data.id
  }
)

์ฃผ์˜: React.memo๋ฅผ ๋‚จ์šฉํ•˜๋ฉด ์˜คํžˆ๋ ค ์„ฑ๋Šฅ์ด ๋‚˜๋น ์งˆ ์ˆ˜ ์žˆ๋‹ค. ๋น„๊ต ์ž์ฒด์—๋„ ๋น„์šฉ์ด ๋“ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์‹ค์ œ๋กœ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค.


14. ์‹ค์ „ ์˜ˆ์ œ: TodoList ์™„์„ฑ ๊ตฌํ˜„

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด Props, State, ๋ถˆ๋ณ€์„ฑ, React.memo๋ฅผ ๋ชจ๋‘ ํ™œ์šฉํ•œ ์™„์„ฑ๋œ TodoList ์˜ˆ์ œ๋‹ค.

import { useState, memo, useCallback } from 'react'

// ํ•  ์ผ ํ•ญ๋ชฉ ์ปดํฌ๋„ŒํŠธ - React.memo๋กœ ์ตœ์ ํ™”
const TodoItem = memo(({ todo, onToggle, onDelete }) => {
  console.log(`TodoItem ๋ Œ๋”๋ง: ${todo.id}`)
  return (
    <li
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: '8px',
        textDecoration: todo.done ? 'line-through' : 'none',
        color: todo.done ? '#aaa' : '#333',
        padding: '8px 0',
      }}
    >
      <input
        type="checkbox"
        checked={todo.done}
        onChange={() => onToggle(todo.id)}
      />
      <span style={{ flex: 1 }}>{todo.text}</span>
      <span style={{ fontSize: '12px', color: '#999' }}>
        {new Date(todo.createdAt).toLocaleDateString()}
      </span>
      <button
        onClick={() => onDelete(todo.id)}
        style={{ color: '#f44336', background: 'none', border: 'none', cursor: 'pointer' }}
      >
        ์‚ญ์ œ
      </button>
    </li>
  )
})

// ์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ
const TodoInput = memo(({ onAdd }) => {
  const [text, setText] = useState('')

  const handleSubmit = (e) => {
    e.preventDefault()
    if (!text.trim()) return
    onAdd(text.trim())
    setText('')
  }

  return (
    <form onSubmit={handleSubmit} style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="ํ•  ์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”..."
        style={{ flex: 1, padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
      />
      <button
        type="submit"
        style={{ padding: '8px 16px', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
      >
        ์ถ”๊ฐ€
      </button>
    </form>
  )
})

// ํ•„ํ„ฐ ์ปดํฌ๋„ŒํŠธ
const TodoFilter = memo(({ filter, onChange }) => {
  return (
    <div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
      {['all', 'active', 'done'].map(f => (
        <button
          key={f}
          onClick={() => onChange(f)}
          style={{
            padding: '4px 12px',
            backgroundColor: filter === f ? '#2196F3' : '#eee',
            color: filter === f ? 'white' : '#333',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
          }}
        >
          {f === 'all' ? '์ „์ฒด' : f === 'active' ? '์ง„ํ–‰ ์ค‘' : '์™„๋ฃŒ'}
        </button>
      ))}
    </div>
  )
})

// ๋ฉ”์ธ TodoList ์ปดํฌ๋„ŒํŠธ
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'React Props ๊ณต๋ถ€ํ•˜๊ธฐ', done: true, createdAt: Date.now() - 86400000 },
    { id: 2, text: 'React State ๊ณต๋ถ€ํ•˜๊ธฐ', done: true, createdAt: Date.now() - 43200000 },
    { id: 3, text: '๋ถˆ๋ณ€์„ฑ ๊ฐœ๋… ์ดํ•ดํ•˜๊ธฐ', done: false, createdAt: Date.now() },
  ])
  const [filter, setFilter] = useState('all')

  // useCallback์œผ๋กœ ํ•จ์ˆ˜ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ (TodoItem ์žฌ๋ Œ๋”๋ง ๋ฐฉ์ง€)
  const handleAdd = useCallback((text) => {
    setTodos(prev => [
      ...prev,
      { id: Date.now(), text, done: false, createdAt: Date.now() }
    ])
  }, [])

  const handleToggle = useCallback((id) => {
    setTodos(prev =>
      prev.map(todo =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    )
  }, [])

  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id))
  }, [])

  // ํ•„ํ„ฐ๋ง๋œ ํ•  ์ผ ๋ชฉ๋ก
  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.done
    if (filter === 'done') return todo.done
    return true
  })

  const doneCount = todos.filter(t => t.done).length

  return (
    <div style={{ maxWidth: '500px', margin: '40px auto', fontFamily: 'sans-serif' }}>
      <h1>Todo List</h1>
      <p style={{ color: '#666' }}>
        {doneCount}/{todos.length} ์™„๋ฃŒ
        {doneCount === todos.length && todos.length > 0 && ' ๋ชจ๋‘ ์™„๋ฃŒ!'}
      </p>

      <TodoInput onAdd={handleAdd} />
      <TodoFilter filter={filter} onChange={setFilter} />

      {filteredTodos.length === 0 ? (
        <p style={{ color: '#999', textAlign: 'center' }}>ํ•  ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
      ) : (
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {filteredTodos.map(todo => (
            <TodoItem
              key={todo.id}
              todo={todo}
              onToggle={handleToggle}
              onDelete={handleDelete}
            />
          ))}
        </ul>
      )}
    </div>
  )
}

export default TodoList

15. ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜ ๋ชจ์Œ

์‹ค์ˆ˜ 1: State๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๊ธฐ

// ์ž˜๋ชป๋œ ์ฝ”๋“œ
const [user, setUser] = useState({ name: 'Amy', age: 30 })

const birthday = () => {
  user.age++        // ์ง์ ‘ ์ˆ˜์ • - React๊ฐ€ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•จ!
  setUser(user)     // ๊ฐ™์€ ์ฐธ์กฐ๋ผ์„œ ๋ฆฌ๋ Œ๋”๋ง ์•ˆ ๋จ
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ
const birthday = () => {
  setUser(prev => ({ ...prev, age: prev.age + 1 }))
}

์‹ค์ˆ˜ 2: ๋น„๋™๊ธฐ ํ•จ์ˆ˜์—์„œ์˜ stale closure

// ๋ฌธ์ œ ์ƒํ™ฉ
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const interval = setInterval(() => {
      // ์—ฌ๊ธฐ์„œ count๋Š” ํ•ญ์ƒ 0! (ํด๋กœ์ €๊ฐ€ ์ดˆ๊ธฐ๊ฐ’์„ ์บก์ฒ˜)
      setCount(count + 1)
    }, 1000)
    return () => clearInterval(interval)
  }, [])  // ๋นˆ ์˜์กด์„ฑ ๋ฐฐ์—ด

  return <p>{count}</p>
}

// ํ•ด๊ฒฐ: ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ ์‚ฌ์šฉ
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(prev => prev + 1)  // ํ•ญ์ƒ ์ตœ์‹  ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ
    }, 1000)
    return () => clearInterval(interval)
  }, [])

  return <p>{count}</p>
}

์‹ค์ˆ˜ 3: ๋ Œ๋”๋ง ์ค‘์— State ๋ณ€๊ฒฝํ•˜๊ธฐ

// ์ž˜๋ชป๋œ ์ฝ”๋“œ - ๋ฌดํ•œ ๋ Œ๋”๋ง ๋ฃจํ”„ ๋ฐœ์ƒ!
function BadComponent() {
  const [count, setCount] = useState(0)
  setCount(count + 1)  // ๋ Œ๋”๋ง ์ค‘์— State ๋ณ€๊ฒฝ!
  return <p>{count}</p>
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ - useEffect๋กœ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ ์ฒ˜๋ฆฌ
function GoodComponent({ initialValue }) {
  const [count, setCount] = useState(initialValue)
  // ๋ Œ๋”๋ง ์ค‘์—๋Š” State ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Œ
  return <p>{count}</p>
}

์‹ค์ˆ˜ 4: key ์—†์ด ๋ฆฌ์ŠคํŠธ ๋ Œ๋”๋ง

// ์ž˜๋ชป๋œ ์ฝ”๋“œ - key ์—†์Œ
todos.map(todo => <TodoItem todo={todo} />)

// ์ž˜๋ชป๋œ ์ฝ”๋“œ - ์ธ๋ฑ์Šค๋ฅผ key๋กœ ์‚ฌ์šฉ (ํ•ญ๋ชฉ์ด ์‚ญ์ œ/์žฌ์ •๋ ฌ๋  ๋•Œ ๋ฌธ์ œ)
todos.map((todo, index) => <TodoItem key={index} todo={todo} />)

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ - ๊ณ ์œ ํ•œ id ์‚ฌ์šฉ
todos.map(todo => <TodoItem key={todo.id} todo={todo} />)

16. Props vs State ์ •๋ฆฌ

๊ตฌ๋ถ„ Props State
์ •์˜ ๋ถ€๋ชจ์—์„œ ์ „๋‹ฌ๋ฐ›๋Š” ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ง์ ‘ ๊ด€๋ฆฌํ•˜๋Š” ๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ
๋ณ€๊ฒฝ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ์ฝ๊ธฐ ์ „์šฉ (์ž์‹์ด ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€) ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ (setState ํ•จ์ˆ˜ ์‚ฌ์šฉ)
๋ณ€๊ฒฝ ์ฃผ์ฒด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ ์ปดํฌ๋„ŒํŠธ ์ž์‹ 
๋ณ€๊ฒฝ ์‹œ ๋ฆฌ๋ Œ๋”๋ง ๋ณ€๊ฒฝ๋˜๋ฉด ์ž์‹ ๋ฆฌ๋ Œ๋”๋ง ๋ณ€๊ฒฝ๋˜๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋”๋ง
์ดˆ๊ธฐ๊ฐ’ ๋ถ€๋ชจ๊ฐ€ ์ „๋‹ฌํ•œ ๊ฐ’ useState()์˜ ์ธ์ž

๋งˆ์น˜๋ฉฐ

Props์™€ State๋Š” React์˜ ํ•ต์‹ฌ์ด๋‹ค. ์ด ๋‘ ๊ฐ€์ง€๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๋ฉด React๋กœ ์–ด๋–ค UI๋“  ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค. ํŠนํžˆ ๋‹ค์Œ ์„ธ ๊ฐ€์ง€๋ฅผ ํ•ญ์ƒ ๋ช…์‹ฌํ•˜์ž.

  1. Props๋Š” ์ฝ๊ธฐ ์ „์šฉ, ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.
  2. State๋Š” ํ•ญ์ƒ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•˜๋ฉฐ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
  3. ์ด์ „ State๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์—…๋ฐ์ดํŠธํ•  ๋•Œ๋Š” ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๋‹ค์Œ ๊ธ€์—์„œ๋Š” React Hooks(useEffect, useRef, useMemo, useCallback, useContext)๋ฅผ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋‹ค๋ฃฌ๋‹ค.



๊ด€๋ จ ๊ธ€

@leekh8
๋ณด์•ˆ, ์›น ๊ฐœ๋ฐœ, Python์„ ๋‹ค๋ฃจ๋Š” ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ