React๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์šฐ๋ฆฌ๊ฐ€ ๊ณ ๋ คํ•˜๊ณ  ์žˆ๋Š” ๋””์ž์ธ์ด๋‚˜ ๋งŒ๋“ค ์•ฑ์— ๋Œ€ํ•œ ์ƒ๊ฐ์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React๋กœ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋นŒ๋“œํ•  ๋•Œ, ๋จผ์ € ์ด๋ฅผ ์ปดํฌ๋„ŒํŠธ๋ผ๋Š” ์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ•๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ ์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘ํ•œ ์‹œ๊ฐ์  ์ƒํƒœ๋“ค์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์—ฐ๊ฒฐํ•˜์—ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ทธ ์‚ฌ์ด๋ฅผ ํ˜๋Ÿฌ๊ฐ€๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ž์Šต์„œ์—์„œ๋Š” React๋กœ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ’ˆ ํ…Œ์ด๋ธ”์„ ๋งŒ๋“œ๋Š” ๊ณผ์ •์„ ์ฒด๊ณ„์ ์œผ๋กœ ์•ˆ๋‚ดํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ชจ์˜ ์‹œ์•ˆ๊ณผ ํ•จ๊ป˜ ์‹œ์ž‘ํ•˜๊ธฐ

์ด๋ฏธ JSON API์™€ ๋””์ž์ด๋„ˆ๋กœ๋ถ€ํ„ฐ ์ œ๊ณต๋ฐ›์€ ๋ชจ์˜ ์‹œ์•ˆ์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค. JSON API๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

[
{ 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" }
]

๋ชจ์˜ ์‹œ์•ˆ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

React๋กœ UI๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค์„ฏ ๊ฐ€์ง€ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์นฉ๋‹ˆ๋‹ค.

Step 1: UI๋ฅผ ์ปดํฌ๋„ŒํŠธ ๊ณ„์ธต์œผ๋กœ ์ชผ๊ฐœ๊ธฐ

๋จผ์ € ๋ชจ์˜ ์‹œ์•ˆ์— ์žˆ๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์™€ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ ์ฃผ๋ณ€์— ๋ฐ•์Šค๋ฅผ ๊ทธ๋ฆฌ๊ณ  ๊ทธ๋“ค์—๊ฒŒ ์ด๋ฆ„์„ ๋ถ™์ด๋ฉด์„œ ์‹œ์ž‘ํ•ด ๋ณด์„ธ์š”. ๋””์ž์ด๋„ˆ์™€ ํ•จ๊ป˜ ์ผํ•œ๋‹ค๋ฉด ๊ทธ๋“ค์ด ์ด๋ฏธ ๋””์ž์ธ ํˆด์„ ํ†ตํ•˜์—ฌ ์ด ์ปดํฌ๋„ŒํŠธ๋“ค์— ์ด๋ฆ„์„ ์ •ํ•ด๋‘์—ˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•œ๋ฒˆ ์—ฌ์ญค๋ณด์„ธ์š”!

์–ด๋–ค ๋ฐฐ๊ฒฝ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋ƒ์— ๋”ฐ๋ผ, ๋””์ž์ธ์„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚˜๋ˆ„๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๊ด€์ ์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Programmingโ€”์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋‚˜ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๋ด…์‹œ๋‹ค. ์ด ์ค‘ ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์„ ๋ฐ˜์˜ํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด ์ปดํฌ๋„ŒํŠธ๋Š” ์ด์ƒ์ ์œผ๋กœ๋Š” ํ•œ ๋ฒˆ์— ํ•œ ๊ฐ€์ง€ ์ผ๋งŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ ์  ์ปค์ง„๋‹ค๋ฉด ์ž‘์€ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์ชผ๊ฐœ์ ธ์•ผ ํ•˜๊ฒ ์ฃ .
  • CSSโ€”ํด๋ž˜์Šค ์„ ํƒ์ž๋ฅผ ๋ฌด์—‡์œผ๋กœ ๋งŒ๋“ค์ง€ ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค. (์‹ค์ œ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์•ฝ๊ฐ„ ๋” ์„ธ๋ถ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.)
  • Designโ€”๋””์ž์ธ ๊ณ„์ธต์„ ์–ด๋–ค ์‹์œผ๋กœ ๊ตฌ์„ฑํ•  ์ง€ ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค.

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

์—ฌ๊ธฐ ๋‹ค์„ฏ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. FilterableProductTable(ํšŒ์ƒ‰): ์˜ˆ์‹œ ์ „์ฒด๋ฅผ ํฌ๊ด„ํ•ฉ๋‹ˆ๋‹ค.
  2. SearchBar(ํŒŒ๋ž€์ƒ‰): ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  3. ProductTable(๋ผ๋ฒค๋”์ƒ‰): ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.
  4. ProductCategoryRow(์ดˆ๋ก์ƒ‰): ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ํ—ค๋”๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
  5. ProductRow(๋…ธ๋ž€์ƒ‰): ๊ฐ๊ฐ์˜ ์ œํ’ˆ์— ํ•ด๋‹นํ•˜๋Š” ํ–‰์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

ProductTable์„ ๋ณด๋ฉด โ€œNameโ€๊ณผ โ€œPriceโ€ ๋ ˆ์ด๋ธ”์„ ํฌํ•จํ•œ ํ…Œ์ด๋ธ” ํ—ค๋” ๊ธฐ๋Šฅ๋งŒ์„ ๊ฐ€์ง„ ์ปดํฌ๋„ŒํŠธ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋…๋ฆฝ๋œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”ฐ๋กœ ์ƒ์„ฑํ•  ์ง€ ์ƒ์„ฑํ•˜์ง€ ์•Š์„์ง€๋Š” ๋‹น์‹ ์˜ ์„ ํƒ์ž…๋‹ˆ๋‹ค. ์ด ์˜ˆ์‹œ์—์„œ๋Š” ProductTable์˜ ์œ„์˜ ๋‹จ์ˆœํ•œ ํ—ค๋”๋“ค์ด ProductTable์˜ ์ผ๋ถ€์ด๊ธฐ ๋•Œ๋ฌธ์— ์œ„ ๋ ˆ์ด๋ธ”๋“ค์„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋งŒ๋“ค์ง€ ์•Š๊ณ  ๊ทธ๋ƒฅ ๋‚จ๊ฒจ๋‘์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด ํ—ค๋”๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด (์ฆ‰ ์ •๋ ฌ์„ ์œ„ํ•œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋“ฑ) ProductTableHeader ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋” ํ•ฉ๋ฆฌ์ ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด์ œ ๋ชจ์˜ ์‹œ์•ˆ ๋‚ด์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ํ™•์ธํ–ˆ์œผ๋‹ˆ, ์ด๋“ค์„ ๊ณ„์ธต ๊ตฌ์กฐ๋กœ ์ •๋ฆฌํ•ด ๋ด…์‹œ๋‹ค. ๋ชจ์˜ ์‹œ์•ˆ์—์„œ ํ•œ ์ปดํฌ๋„ŒํŠธ ๋‚ด์— ์žˆ๋Š” ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋Š” ๊ณ„์ธต ๊ตฌ์กฐ์—์„œ ์ž์‹์œผ๋กœ ํ‘œํ˜„๋ฉ๋‹ˆ๋‹ค.

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

Step 2: React๋กœ ์ •์ ์ธ ๋ฒ„์ „ ๊ตฌํ˜„ํ•˜๊ธฐ

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

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

์•ฑ์„ ๋งŒ๋“ค ๋•Œ ๊ณ„์ธต ๊ตฌ์กฐ์— ๋”ฐ๋ผ ์ƒ์ธต๋ถ€์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ (์ฆ‰, FilterableProductTable๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ)๋ถ€ํ„ฐ ํ•˜ํ–ฅ์‹Top-Down์œผ๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜ ํ˜น์€ ํ•˜์ธต๋ถ€์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ (ProductRow)๋ถ€ํ„ฐ ์ƒํ–ฅ์‹Bottom-Up์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ์—์„œ๋Š” ๋ณดํ†ต ํ•˜ํ–ฅ์‹์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒŒ ์‰ฝ์ง€๋งŒ, ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ง€๋ฉด ์ƒํ–ฅ์‹์œผ๋กœ ๋งŒ๋“ค๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ์ด ๋” ์‰ฝ์Šต๋‹ˆ๋‹ค.

function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">
        {category}
      </th>
    </tr>
  );
}

function ProductRow({ product }) {
  const name = product.stocked ? product.name :
    <span style={{ color: 'red' }}>
      {product.name}
    </span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category} />
      );
    }
    rows.push(
      <ProductRow
        product={product}
        key={product.name} />
    );
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

function SearchBar() {
  return (
    <form>
      <input type="text" placeholder="Search..." />
      <label>
        <input type="checkbox" />
        {' '}
        Only show products in stock
      </label>
    </form>
  );
}

function FilterableProductTable({ products }) {
  return (
    <div>
      <SearchBar />
      <ProductTable products={products} />
    </div>
  );
}

const PRODUCTS = [
  {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"}
];

export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

(์œ„ ์ฝ”๋“œ๊ฐ€ ์–ด๋ ต๊ฒŒ ๋А๊ปด์ง„๋‹ค๋ฉด, ๋น ๋ฅด๊ฒŒ ์‹œ์ž‘ํ•˜๊ธฐ๋ฅผ ๋จผ์ € ์ฐธ๊ณ ํ•˜์„ธ์š”!)

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

์ฃผ์˜ํ•˜์„ธ์š”!

์—ฌ๊ธฐ๊นŒ์ง€๋Š” ์•„์ง State ๊ฐ’์„ ์“ฐ์ง€ ๋งˆ์„ธ์š”. ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ ์‚ฌ์šฉํ•  ๊ฒ๋‹ˆ๋‹ค!

Step 3: ์ตœ์†Œํ•œ์˜ ๋ฐ์ดํ„ฐ๋งŒ ์ด์šฉํ•ด์„œ ์™„๋ฒฝํ•˜๊ฒŒ UI State ํ‘œํ˜„ํ•˜๊ธฐ

UI๋ฅผ ์ƒํ˜ธ์ž‘์šฉInteractiveํ•˜๊ฒŒ ๋งŒ๋“ค๋ ค๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. React๋Š” State๋ฅผ ํ†ตํ•ด ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

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

์˜ˆ์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด ๋ฐ์ดํ„ฐ๋“ค์„ ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ œํ’ˆ์˜ ์›๋ณธ ๋ชฉ๋ก
  2. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฒ€์ƒ‰์–ด
  3. ์ฒดํฌ๋ฐ•์Šค์˜ ๊ฐ’
  4. ํ•„ํ„ฐ๋ง๋œ ์ œํ’ˆ ๋ชฉ๋ก

์ด ์ค‘ ์–ด๋–ค ๊ฒŒ State๊ฐ€ ๋˜์–ด์•ผ ํ• ๊นŒ์š”? ์•„๋ž˜์˜ ์„ธ ๊ฐ€์ง€ ์งˆ๋ฌธ์„ ํ†ตํ•ด ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์‹œ๊ฐ„์ด ์ง€๋‚˜๋„ ๋ณ€ํ•˜์ง€ ์•Š๋‚˜์š”? ๊ทธ๋Ÿฌ๋ฉด ํ™•์‹คํžˆ State๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
  • ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ Props๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋ฉ๋‹ˆ๊นŒ? ๊ทธ๋Ÿฌ๋ฉด ํ™•์‹คํžˆ State๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ ์•ˆ์˜ ๋‹ค๋ฅธ State๋‚˜ Props๋ฅผ ๊ฐ€์ง€๊ณ  ๊ณ„์‚ฐ ๊ฐ€๋Šฅํ•œ๊ฐ€์š”? ๊ทธ๋ ‡๋‹ค๋ฉด ์ ˆ๋Œ€๋กœ State๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!

๊ทธ ์™ธ ๋‚จ๋Š” ๊ฑด ์•„๋งˆ State์ผ ๊ฒ๋‹ˆ๋‹ค.

์œ„ ๋ฐ์ดํ„ฐ๋“ค์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ˆœ์„œ๋Œ€๋กœ ์‚ดํŽด๋ด…์‹œ๋‹ค.

  1. ์ œํ’ˆ์˜ ์›๋ณธ ๋ชฉ๋ก์€ Props๋กœ ์ „๋‹ฌ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— State๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
  2. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฒ€์ƒ‰์–ด๋Š” ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ•˜๊ณ , ๋‹ค๋ฅธ ์š”์†Œ๋กœ๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— State๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ์ฒดํฌ๋ฐ•์Šค์˜ ๊ฐ’์€ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ฐ”๋€Œ๊ณ  ๋‹ค๋ฅธ ์š”์†Œ๋กœ๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— State๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  4. ํ•„ํ„ฐ๋ง๋œ ์ œํ’ˆ ๋ชฉ๋ก์€ ์›๋ณธ ์ œํ’ˆ ๋ชฉ๋ก์„ ๋ฐ›์•„์„œ ๊ฒ€์ƒ‰์–ด์™€ ์ฒดํฌ๋ฐ•์Šค์˜ ๊ฐ’์— ๋”ฐ๋ผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ด๋Š” State๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ๊ฒ€์ƒ‰์–ด์™€ ์ฒดํฌ๋ฐ•์Šค์˜ ๊ฐ’๋งŒ์ด State์ž…๋‹ˆ๋‹ค! ์ž˜ํ•˜์…จ์Šต๋‹ˆ๋‹ค!

์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ธฐ

Props vs State

React๋Š” Props์™€ State๋ผ๋Š” ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ โ€œ๋ชจ๋ธโ€์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๋‘˜์˜ ์„ฑ๊ฒฉ์€ ๋งค์šฐ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

Props์™€ State๋Š” ๋‹ค๋ฅด์ง€๋งŒ, ํ•จ๊ป˜ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. State๋Š” ๋ณดํ†ต ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. (๊ทธ๋ž˜์„œ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋Š” ๊ทธ State๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.) ๊ทธ๋ฆฌ๊ณ  ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋Š” State๋ฅผ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— Props๋กœ์„œ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ฒ˜์Œ ๋ดค์„ ๋•Œ ๋‘˜์˜ ์ฐจ์ด๋ฅผ ์ž˜ ์•Œ๊ธฐ ์–ด๋ ค์›Œ๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์•ฝ๊ฐ„ ์—ฐ์Šต์ด ํ•„์š”ํ•  ๊ฑฐ์˜ˆ์š”!

Step 4: State๊ฐ€ ์–ด๋””์— ์žˆ์–ด์•ผ ํ•  ์ง€ ์ •ํ•˜๊ธฐ

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

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฐ State์— ๋Œ€ํ•ด์„œ,

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

์ด์ „ ๋‹จ๊ณ„์—์„œ, ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‘ ๊ฐ€์ง€ State์ธ ์‚ฌ์šฉ์ž์˜ ๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ๊ณผ ์ฒดํฌ ๋ฐ•์Šค์˜ ๊ฐ’์„ ๋ฐœ๊ฒฌํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์‹œ์—์„œ ๊ทธ๋“ค์€ ํ•ญ์ƒ ํ•จ๊ป˜ ๋‚˜ํƒ€๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ™์€ ์œ„์น˜์— ๋‘๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค.

์ด์ œ ์ด ์ „๋žต์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ์šฉํ•ด ๋ด…์‹œ๋‹ค.

  1. State๋ฅผ ์“ฐ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐพ์•„๋ด…์‹œ๋‹ค:
    • ProductTable์€ State์— ๊ธฐ๋ฐ˜ํ•œ ์ƒํ’ˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ํ•„ํ„ฐ๋งํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (๊ฒ€์ƒ‰์–ด์™€ ์ฒดํฌ ๋ฐ•์Šค์˜ ๊ฐ’)
    • SearchBar๋Š” State๋ฅผ ํ‘œ์‹œํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (๊ฒ€์ƒ‰์–ด์™€ ์ฒดํฌ ๋ฐ•์Šค์˜ ๊ฐ’)
  2. ๊ณตํ†ต ๋ถ€๋ชจ๋ฅผ ์ฐพ์•„๋ด…์‹œ๋‹ค: ๋‘˜ ๋ชจ๋‘๊ฐ€ ๊ณต์œ ํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ๋ถ€๋ชจ๋Š” FilterableProductTable์ž…๋‹ˆ๋‹ค.
  3. ์–ด๋””์— State๊ฐ€ ์กด์žฌํ•ด์•ผ ํ• ์ง€ ์ •ํ•ด๋ด…์‹œ๋‹ค: ์šฐ๋ฆฌ๋Š”FilterableProductTable์— ๊ฒ€์ƒ‰์–ด์™€ ์ฒดํฌ ๋ฐ•์Šค ๊ฐ’์„ State๋กœ ๋‘˜ ๊ฒ๋‹ˆ๋‹ค.

์ด์ œ State ๊ฐ’์€ FilterableProductTable ์•ˆ์— ์žˆ์Šต๋‹ˆ๋‹ค.

useState() Hook์„ ์ด์šฉํ•ด์„œ State๋ฅผ ์ปดํฌ๋„ŒํŠธ์— ์ถ”๊ฐ€ํ•˜์„ธ์š”. Hook์€ React ๊ธฐ๋Šฅ์— โ€œ์—ฐ๊ฒฐํ•  ์ˆ˜Hook Intoโ€ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํŠน๋ณ„ํ•œ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. FilterableProductTable์˜ ์ƒ๋‹จ์— ๋‘ ๊ฐœ์˜ State ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ์ดˆ๊นƒ๊ฐ’์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์—ฌ์ฃผ์„ธ์š”.

function FilterableProductTable({ products }) {
const [filterText, setFilterText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);

๋‹ค์Œ์œผ๋กœ, filterText์™€ inStockOnly๋ฅผ ProductTable์™€ SearchBar์—๊ฒŒ Props๋กœ ์ „๋‹ฌํ•˜์„ธ์š”.

<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly} />
<ProductTable
products={products}
filterText={filterText}
inStockOnly={inStockOnly} />
</div>

์ด์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. filterText์˜ ์ดˆ๊นƒ๊ฐ’์„ useState('')์—์„œ useState('fruit')๋กœ ์ˆ˜์ •ํ•ด ๋ณด์„ธ์š”. ๊ฒ€์ƒ‰์ฐฝ๊ณผ ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”์ด ๋ชจ๋‘ ์—…๋ฐ์ดํŠธ๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState } from 'react';

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);
  return (
    <div>
      <SearchBar
        filterText={filterText}
        inStockOnly={inStockOnly} />
      <ProductTable
        products={products}
        filterText={filterText}
        inStockOnly={inStockOnly} />
    </div>
  );
}
function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">
        {category}
      </th>
    </tr>
  );
}
function ProductRow({ product }) {
  const name = product.stocked ? product.name :
    <span style={{ color: 'red' }}>
      {product.name}
    </span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products, filterText, inStockOnly }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (
      product.name.toLowerCase().indexOf(
        filterText.toLowerCase()
      ) === -1
    ) {
      return;
    }
    if (inStockOnly && !product.stocked) {
      return;
    }
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category} />
      );
    }
    rows.push(
      <ProductRow
        product={product}
        key={product.name} />
    );
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

function SearchBar({ filterText, inStockOnly }) {
  return (
    <form>
      <input
        type="text"
        value={filterText}
        placeholder="Search..."/>
      <label>
        <input
          type="checkbox"
          checked={inStockOnly} />
        {' '}
        Only show products in stock
      </label>
    </form>
  );
}

const PRODUCTS = [
  {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"}
];

export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

์•„์ง ํผ์„ ์ˆ˜์ •ํ•˜๋Š” ์ž‘์—…์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Œ์— ์œ ์˜ํ•˜์„ธ์š”. ์œ„ ์ƒŒ๋“œ๋ฐ•์Šค์—์„œ ์ฝ˜์†” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ, ๊ทธ ์ด์œ ๋ฅผ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

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

์œ„์— ์žˆ๋Š” ์ƒŒ๋“œ๋ฐ•์Šค๋ฅผ ๋ณด๋ฉด, ProductTable๊ณผ SearchBar๊ฐ€ filterText์™€ inStockOnly Props๋ฅผ ํ‘œTable, ์ž…๋ ฅ์ฐฝInput, ์ฒดํฌ ๋ฐ•์Šค๋ฅผ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ฝ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, SearchBar ์ž…๋ ฅ์ฐฝ์˜ value๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ฑ„์šฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

function SearchBar({ filterText, inStockOnly }) {
return (
<form>
<input
type="text"
value={filterText}
placeholder="Search..."/>

๊ทธ๋Ÿฌ๋‚˜ ์•„์ง ์‚ฌ์šฉ์ž์˜ ํ‚ค๋ณด๋“œ ์ž…๋ ฅ๊ณผ ๊ฐ™์€ ํ–‰๋™์— ๋ฐ˜์‘ํ•˜๋Š” ์ฝ”๋“œ๋Š” ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์€ ๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„์—์„œ ์ง„ํ–‰ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

Step 5: ์—ญ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ถ”๊ฐ€ํ•˜๊ธฐ

์ง€๊ธˆ๊นŒ์ง€ ์šฐ๋ฆฌ๋Š” ๊ณ„์ธต ๊ตฌ์กฐ ์•„๋ž˜๋กœ ํ๋ฅด๋Š” Props์™€ State์˜ ํ•จ์ˆ˜๋กœ์จ ์•ฑ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋”ฐ๋ผ State๋ฅผ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๊ณ„์ธต ๊ตฌ์กฐ์˜ ํ•˜๋‹จ์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ FilterableProductTable์˜ State๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

React๋Š” ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋ช…์‹œ์ ์œผ๋กœ ๋ณด์ด๊ฒŒ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋Š” ์ „ํ†ต์ ์ธ ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ๋ณด๋‹ค ์กฐ๊ธˆ ๋” ๋งŽ์€ ํƒ€์ดํ•‘์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

4๋‹จ๊ณ„์˜ ์˜ˆ์‹œ์—์„œ ์ฒดํฌํ•˜๊ฑฐ๋‚˜ ํ‚ค๋ณด๋“œ๋ฅผ ํƒ€์ดํ•‘ํ•  ๊ฒฝ์šฐ UI์˜ ๋ณ€ํ™”๊ฐ€ ์—†๊ณ  ์ž…๋ ฅ์„ ๋ฌด์‹œํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฑด ์˜๋„์ ์œผ๋กœ <input value={filterText} />๋กœ ์ฝ”๋“œ๋ฅผ ์“ฐ๋ฉด์„œ value๋ผ๋Š” Prop์ด ํ•ญ์ƒFilterableProductTable์˜ filterText๋ผ๋Š” State๋ฅผ ํ†ตํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋„๋ก ์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. filterText๋ผ๋Š” State๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒŒ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— input์˜ value๋Š” ๋ณ€ํ•˜์ง€ ์•Š๊ณ  ํ™”๋ฉด๋„ ๋ฐ”๋€Œ๋Š” ๊ฒŒ ์—†์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ input์„ ๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก State๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. State๋Š” FilterableProductTable์ด ๊ฐ€์ง€๊ณ  ์žˆ๊ณ  State ๋ณ€๊ฒฝ์„ ์œ„ํ•ด์„œ๋Š” setFilterText์™€ setInStockOnly๋ฅผ ํ˜ธ์ถœ์„ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. SearchBar๊ฐ€ FilterableProductTable์˜ State๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ ค๋ฉด, ์ด ํ•จ์ˆ˜๋“ค์„ SearchBar๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

function FilterableProductTable({ products }) {
const [filterText, setFilterText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);

return (
<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
onFilterTextChange={setFilterText}
onInStockOnlyChange={setInStockOnly} />

SearchBar์—์„œ onChange ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ถ€๋ชจ State๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function SearchBar({
filterText,
inStockOnly,
onFilterTextChange,
onInStockOnlyChange
}) {
return (
<form>
<input
type="text"
value={filterText}
placeholder="Search..."
onChange={(e) => onFilterTextChange(e.target.value)}
/>
<label>
<input
type="checkbox"
checked={inStockOnly}
onChange={(e) => onInStockOnlyChange(e.target.checked)}

์ด์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์™„์ „ํžˆ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค!

import { useState } from 'react';

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

  return (
    <div>
      <SearchBar
        filterText={filterText}
        inStockOnly={inStockOnly}
        onFilterTextChange={setFilterText}
        onInStockOnlyChange={setInStockOnly} />
      <ProductTable
        products={products}
        filterText={filterText}
        inStockOnly={inStockOnly} />
    </div>
  );
}

function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">
        {category}
      </th>
    </tr>
  );
}

function ProductRow({ product }) {
  const name = product.stocked ? product.name :
    <span style={{ color: 'red' }}>
      {product.name}
    </span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products, filterText, inStockOnly }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (
      product.name.toLowerCase().indexOf(
        filterText.toLowerCase()
      ) === -1
    ) {
      return;
    }
    if (inStockOnly && !product.stocked) {
      return;
    }
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category} />
      );
    }
    rows.push(
      <ProductRow
        product={product}
        key={product.name} />
    );
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

function SearchBar({
  filterText,
  inStockOnly,
  onFilterTextChange,
  onInStockOnlyChange
}) {
  return (
    <form>
      <input
        type="text"
        value={filterText} placeholder="Search..."
        onChange={(e) => onFilterTextChange(e.target.value)} />
      <label>
         <input
          type="checkbox"
          checked={inStockOnly}
          onChange={(e) => onInStockOnlyChange(e.target.checked)} />
        {' '}
        Only show products in stock
      </label>
    </form>
  );
}

const PRODUCTS = [
  {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"}
];

export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

์ƒํ˜ธ์ž‘์šฉ ์ถ”๊ฐ€ํ•˜๊ธฐ ์„น์…˜์—์„œ State๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ์ด๋ฒคํŠธ๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ๋” ๊นŠ์ด์žˆ๊ฒŒ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋” ๋‚˜์•„๊ฐ€๊ธฐ

์ง€๊ธˆ๊นŒ์ง€๋Š” React๋ฅผ ์ด์šฉํ•ด์„œ ์ปดํฌ๋„ŒํŠธ์™€ ์•ฑ์„ ๋งŒ๋“ค๋ ค๊ณ  ํ•  ๋•Œ ์–ด๋–ป๊ฒŒ ์‚ฌ๊ณ ํ• ์ง€์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์†Œ๊ฐœํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹น์žฅ React๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ด๋„ ์ข‹๊ณ  ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ€์„œ ์ด ์ž์Šต์„œ๋ฅผ ์ด์šฉํ•ด์„œ ์ข€ ๋” ์‹ฌํ™” ํ•™์Šตํ•ด๋„ ์ข‹์Šต๋‹ˆ๋‹ค.