๐Ÿ“Ž React

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

Learn React - Managing State

Reacting-to-input-with-state

  • React๋Š” ์„ ์–ธํ˜• UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ํƒ์‹œ๊ธฐ์‚ฌ์—๊ฒŒ ๋ชฉ์ ์ง€๋งŒ ๋งํ•˜๋ฉด ์•Œ์•„์„œ ๊ฐ€๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, React๋Š” ์ƒํƒœ์— ๋”ฐ๋ผ UI๋ฅผ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ

Thinking about UI declaratively

  • ์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘ํ•œ ์‹œ๊ฐ์  ์ƒํƒœ๋ฅผ ์‹๋ณ„ํ•œ๋‹ค.
  • ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์ด‰๋ฐœํ•˜๋Š” ์š”์†Œ๋ฅผ ํŒŒ์•…ํ•œ๋‹ค.
  • useState๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์˜ ์ƒํƒœ๋ฅผ ํ‘œํ˜„ํ•œ๋‹ค.
  • ๋น„ํ•„์ˆ˜์ ์ธ state ๋ณ€์ˆ˜๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.
  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ state๋ฅผ ์„ค์ •ํ•œ๋‹ค.

Step 1: Identify your componentโ€™s different visual states

Displaying many visual states at once

์ปดํฌ๋„ŒํŠธ์— ์‹œ๊ฐ์  ์ƒํƒœ๊ฐ€ ๋งŽ์€ ๊ฒฝ์šฐ ํ•œ ํŽ˜์ด์ง€์— ๋ชจ๋‘ ํ‘œ์‹œํ•ด๋ณด๋Š” ๊ฒƒ์ด ํŽธ๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.(๊ฐœ์ธ์ ์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ์†์œผ๋กœ๋งŒ ์ž‘์—…ํ•ด๋‘๋Š” ๊ฒŒ ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.) ์ด๋Ÿฐ ํŽ˜์ด์ง€๋ฅผ โ€˜living style guideโ€™ ํ˜น์€ โ€˜storybookโ€™์ด๋ผ๊ณ  ํ•œ๋‹ค.

import Form from './Form.js';

let statuses = [
  'empty',
  'typing',
  'submitting',
  'success',
  'error',
];

export default function App() {
  return (
    <>
      {statuses.map(status => (
        <section key={status}>
          <h4>Form ({status}):</h4>
          <Form status={status} />
        </section>
      ))}
    </>
  );
}

Step 2: Determine what triggers those state changes

์ƒํƒœ ๋ณ€๊ฒฝ์„ ์ผ์œผํ‚ค๋Š” ์š”์†Œ๋ฅผ ํŒŒ์•…ํ•ด์•ผํ•œ๋‹ค. ํฌ๊ฒŒ 2๊ฐ€์ง€๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

  • ์‚ฌ๋žŒ์˜ ์ž…๋ ฅ : ๋ฒ„ํŠผ ํด๋ฆญ, ํ•„๋“œ ์ž…๋ ฅ, ๋งํฌ ์ด๋™ ๋“ฑ
  • ์ปดํ“จํ„ฐ์˜ ์ž…๋ ฅ : ๋„คํŠธ์›Œํฌ์—์„œ ์‘๋‹ต ๋„์ฐฉ, ์‹œ๊ฐ„ ์ดˆ๊ณผ, ์ด๋ฏธ์ง€ ๋กœ๋”ฉ ๋“ฑ

์‚ฌ๋žŒ์˜ ์ž…๋ ฅ์—๋Š” ์ข…์ข… ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค!(handleXXX)

Step 3: Represent the state in memory with useState

์ด์ œ ๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ์˜ ๋ฉ”๋ชจ๋ฆฌ ์—ญํ• ์„ ํ•˜๋Š” state๋ฅผ useState๋กœ ์ •์˜ํ•ด์•ผํ•œ๋‹ค. ์ด ๋•Œ, ๊ฐ€์žฅ ์ตœ์†Œํ•œ์˜ state๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ state๋ถ€ํ„ฐ ์ •์˜ํ•˜๋„๋ก ํ•˜์ž! ์ฆ‰์‹œ state๊ฐ€ ์ •๋ฆฌ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์ผ๋‹จ ๊ฐ€๋Šฅํ•œ state๋ฅผ ๋‹ค ์ ์–ด๋ณด๊ณ  ํ•˜๋‚˜์”ฉ ์ณ๋‚ด๋ฉด ๋œ๋‹ค.

Step 4: Remove any non-essential state variables

์ด ๋‹จ๊ณ„์—์„œ์˜ ๋ชฉํ‘œ๋Š” state๊ฐ€ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ฃผ๊ธฐ๋ฅผ ์›ํ•˜๋Š” UI๋ฅผ ๋ณด์—ฌ์ฃผ์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์•„๋ž˜์˜ 3๊ฐ€์ง€๋ฅผ ๊ณ ๋ คํ•ด๋ณด์ž.

  • state๊ฐ€ ๋ชจ์ˆœ์„ ์•ผ๊ธฐํ•˜๋Š”์ง€?(ex. isSubmitting๊ณผ isSuccess๊ฐ€ ๋™์‹œ์— true์ธ ๊ฒฝ์šฐ)
  • ๋‹ค๋ฅธ state์— ์ด๋ฏธ ๊ฐ™์€ ์ •๋ณด๊ฐ€ ์žˆ๋Š”์ง€?
  • ๋‹ค๋ฅธ state๋ฅผ ๋’ค์ง‘์œผ๋ฉด ๊ฐ™์€ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋Š”์ง€?

Eliminating โ€œimpossibleโ€ states with a reducer

  • useReducer๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด state๋ฅผ ๋” ์ž˜ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์กฐ๊ธˆ ๋” ์ •ํ™•ํ•˜๊ฒŒ state๋ฅผ ๋ชจ๋ธ๋งํ•˜๊ธฐ ์œ„ํ•ด useReducer๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Step 5: Connect the event handlers to set state

์ด์ œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ state๋ฅผ ์„ค์ •ํ•ด๋ณด์ž. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” handleXXX์™€ ๊ฐ™์€ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Choosing the State Structure

Principles for structuring state

state๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค. ์ด ๋•Œ, state๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๋Š” ๋ฐ ์žˆ์–ด์„œ ๋ช‡ ๊ฐ€์ง€ ์›์น™์„ ์ง€ํ‚ค๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

  • ๊ด€๋ จ state๋ฅผ ๊ทธ๋ฃนํ™”ํ•ด๋ผ. ํ•ญ์ƒ ๋‘ ๊ฐœ ์ด์ƒ์˜ state ๋ณ€์ˆ˜๋ฅผ ๋™์‹œ์— ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒฝ์šฐ ๋‹จ์ผ state ๋ณ€์ˆ˜๋กœ ๋ณ‘ํ•ฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
  • state์˜ ๋ชจ์ˆœ์„ ํ”ผํ•ด๋ผ.
  • ๋ถˆํ•„์š”ํ•œ state๋ฅผ ํ”ผํ•ด๋ผ.
  • state ์ค‘๋ณต์„ ํ”ผํ•ด๋ผ.
  • ๊นŠ๊ฒŒ ์ค‘์ฒฉ๋œ state๋Š” ํ”ผํ•ด๋ผ.

์œ„์˜ ๊ณผ์ •์€ DB ์—”์ง€๋‹ˆ์–ด๊ฐ€ ๋ฒ„๊ทธ๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด DB๋ฅผ ์ •๊ทœํ™”ํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•œ ์ž‘์—…์ด๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด handleXXX์˜ ํ•จ์ˆ˜์—์„œ event ์ธ์ž๋Š” ํ•ญ์ƒ ๋งˆ์ง€๋ง‰์— ์œ„์น˜ํ•ด์•ผํ•œ๋‹ค.

function handleItemChange(id, e) {
    setItems(items.map(item => {
      if (item.id === id) {
        return {
          ...item,
          title: e.target.value,
        };
      } else {
        return item;
      }
    }));
  }

Sharing State Between Components

Lifting state up

๋‘ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ผํ•œ state๋ฅผ ๊ณต์œ ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ, state๋ฅผ ๋‘ ์ปดํฌ๋„ŒํŠธ์˜ ๊ณตํ†ต ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋กœ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ๊ทธ ์ ˆ์ฐจ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—์„œ state๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.
  • ๊ณตํ†ต ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— ํ•˜๋“œ ์ฝ”๋”ฉ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.
  • ๊ณตํ†ต ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— state๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ํ•จ๊ป˜ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•œ๋‹ค.

Controlled and uncontrolled components

์ผ๋ฐ˜์ ์œผ๋กœ ์ผ๋ถ€ ๋กœ์ปฌ state๋ฅผ ๊ฐ€์ง„ ์ปดํฌ๋„ŒํŠธ๋ฅผ โ€˜uncontrolled componentโ€™๋ผ๊ณ  ํ•œ๋‹ค. ๋ฐ˜๋ฉด, ๋ชจ๋“  state๋ฅผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋กœ ์˜ฎ๊ธด ์ปดํฌ๋„ŒํŠธ๋ฅผ โ€˜controlled componentโ€™๋ผ๊ณ  ํ•œ๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ <input />์˜ value๊ฐ€ state์— ์˜ํ•ด ๊ฒฐ์ •๋˜๋Š” ๊ฒฝ์šฐ controlled component๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด uncontrolled component๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

A single source of truth for each state

state๋ฅผ ๊ณต์œ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก state๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹ค. ์ด ๋•Œ, ๊ฐ ๊ณ ์œ ํ•œ state๋“ค์— ๋Œ€ํ•ด ํ•ด๋‹น state๋ฅผ โ€œ์†Œ์œ โ€ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ ํƒํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ โ€˜single source of truthโ€™๋ผ๊ณ  ํ•œ๋‹ค. ์ด ๋•Œ, state๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” state๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•ด์•ผํ•œ๋‹ค. ์ด ๋•Œ, ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋Š” state๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ state๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹จ์ˆœํ•˜๊ฒŒ ๋งํ•˜๋ฉด ๊ฐ state์— ๋Œ€ํ•œ ๊ณต๊ธ‰์› ์—ญํ• ์„ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฒฐ์ •ํ•ด์•ผํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ์ด ๋•Œ, state ๋Œ์–ด์˜ฌ๋ฆฌ๊ธฐ ๊ธฐ๋ฒ•์ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

Preserving and Resetting State

state๋Š” ์ปดํฌ๋„ŒํŠธ ๊ฐ„์— ๊ฒฉ๋ฆฌ๋œ๋‹ค. React๋Š” UI ํŠธ๋ฆฌ์—์„œ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ด๋–ค state์— ์†ํ•˜๋Š”์ง€๋ฅผ ์ถ”์ ํ•œ๋‹ค. state๋ฅผ ์–ธ์ œ ๋ณด์กดํ•˜๊ณ  ์–ธ์ œ ์ดˆ๊ธฐํ™”ํ• ์ง€๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.

The UI tree

๋ธŒ๋ผ์šฐ์ €๋Š” UI๋ฅผ ๋ชจ๋ธ๋งํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜๋งŽ์€ ํŠธ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. DOM์€ HTML ์š”์†Œ๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ , CSSOM์€ CSS์— ๋Œ€ํ•ด ๋™์ผํ•œ ์—ญํ• ์„ ํ•œ๋‹ค. ์‹ฌ์ง€์–ด ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ๋„ ์กด์žฌํ•œ๋‹ค!

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ฆฌ์•กํŠธ๋„ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•œ UI๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ๋ชจ๋ธ๋งํ•œ๋‹ค. ๋ฆฌ์•กํŠธ๋Š” JSX๋กœ๋ถ€ํ„ฐ UI ํŠธ๋ฆฌ๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค. ๊ทธ ๋‹ค์Œ ๋ฆฌ์•กํŠธ DOM์ด ํ•ด๋‹น UI ํŠธ๋ฆฌ์™€ ์ผ์น˜ํ•˜๋„๋ก ๋ธŒ๋ผ์šฐ์ € DOM ์—˜๋ฆฌ๋จผํŠธ๋“ค์„ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.(React Native๋Š” ํŠธ๋ฆฌ๋ฅผ ๋ชจ๋ฐ”์ผ ํ”Œ๋žซํผ์— ๋งž๋Š” ์—˜๋ฆฌ๋จผํŠธ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.)

State is tied to a position in the tree

ํ”ํžˆ state๊ฐ€ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— ์œ„์น˜ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ state๋Š” ๋ฆฌ์•กํŠธ ๋‚ด๋ถ€์— ์œ„์น˜ํ•œ๋‹ค. ์ฆ‰, state๋Š” ๋ฆฌ์•กํŠธ๊ฐ€ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ์œ„์น˜์— ๋”ฐ๋ผ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ˆ์ œ ์ฝ”๋“œ์—์„œ ๊ฐ๊ฐ์˜ Counter ์ปดํฌ๋„ŒํŠธ์˜ score๋ฅผ ์ฆ๊ฐ€์‹œํ‚จ ํ›„, 2๋ฒˆ์งธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ฑฐํ–ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ์ƒ์„ฑํ•˜๋ฉด 2๋ฒˆ์งธ ์ปดํฌ๋„ŒํŠธ์˜ score๊ฐ€ ์ดˆ๊ธฐํ™”๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฆฌ์•กํŠธ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ UI ํŠธ๋ฆฌ์˜ ํ•ด๋‹น ์œ„์น˜์—์„œ ๋ Œ๋”๋ง๋˜๋Š” ๋™์•ˆ ์ปดํฌ๋„ŒํŠธ์˜ state๋ฅผ ์œ ์ง€ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ œ๊ฑฐ๋˜๊ฑฐ๋‚˜ ๊ฐ™์€ ์œ„์น˜์— ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๋ฉด ๋ฆฌ์•กํŠธ๋Š” ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ state๋ฅผ ์‚ญ์ œํ•œ๋‹ค.

import { useState } from 'react';

export default function App() {
  const [showB, setShowB] = useState(true);
  return (
    <div>
      <Counter />
      {showB && <Counter />} 
      <label>
        <input
          type="checkbox"
          checked={showB}
          onChange={e => {
            setShowB(e.target.checked)
          }}
        />
        Render the second counter
      </label>
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

Same component at the same position preserves state

์œ„์˜ ์˜ˆ์ œ์™€ ๋‹ค๋ฅด๊ฒŒ ๋™์ผํ•œ ์œ„์น˜์— ์žˆ๋Š” ๋™์ผํ•œ ์ปดํฌ๋„ŒํŠธ๋Š” state๋ฅผ ๋ณด์กดํ•œ๋‹ค.

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

์ฒดํฌ๋ฐ•์Šค๋ฅผ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ์„ ํƒ ์ทจ์†Œํ•ด๋„ ์นด์šดํ„ฐ state๋Š” ์žฌ์„ค์ •๋˜์ง€ ์•Š๋Š”๋‹ค. isFancy๊ฐ€ true์ด๋“  false์ด๋“ , ๋ฃจํŠธ App ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ˜ํ™˜๋œ div์˜ ์ฒซ ๋ฒˆ์งธ ์ž์‹์—๋Š” ํ•ญ์ƒ ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋ฆฌ์•กํŠธ์—์„œ ์ปดํฌ๋„ŒํŠธ์˜ ์œ„์น˜๊ฐ€ ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ์œ„์น˜์ด๋‹ค. ์ฆ‰, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ์œ„์น˜๊ฐ€ ๋™์ผํ•˜๋‹ค๋ฉด state๋ฅผ ๋ณด์กดํ•œ๋‹ค.(JSX ๋งˆํฌ์—…์ด ์ค‘์š”ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค!)

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  if (isFancy) {
    return (
      <div>
        <Counter isFancy={true} />
        <label>
          <input
            type="checkbox"
            checked={isFancy}
            onChange={e => {
              setIsFancy(e.target.checked)
            }}
          />
          Use fancy styling
        </label>
      </div>
    );
  }
  return (
    <div>
      <Counter isFancy={false} />
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

์œ„ ์˜ˆ์ œ์—์„œ checkbox๋ฅผ ์„ ํƒํ•˜๋ฉด state๊ฐ€ ์žฌ์„ค์ •๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ทธ๋ ‡์ง€ ์•Š๋‹ค. ์ด ๋‘ ํƒœ๊ทธ๊ฐ€ ๋ชจ๋‘ ๊ฐ™์€ ์œ„์น˜์— ๋ Œ๋”๋ง๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. React๋Š” ํ•จ์ˆ˜์—์„œ ์กฐ๊ฑด์„ ์–ด๋””์— ๋ฐฐ์น˜ํ–ˆ๋Š”์ง€ ์•Œ์ง€ ๋ชปํ•˜๊ณ  ๋‹จ์ง€ ์—ฌ๋Ÿฌ๋ถ„์ด ๋ฐ˜ํ™˜ํ•˜๋Š” ํŠธ๋ฆฌ๋งŒ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ App ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฅผ ์ฒซ ๋ฒˆ์งธ ์ž์‹์œผ๋กœ ๊ฐ€์ง„

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

Different components at the same position reset state

๋ฐ˜๋ฉด ๋™์ผํ•œ ์œ„์น˜๋”๋ผ๋„ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋ฉด state๊ฐ€ ์žฌ์„ค์ •๋œ๋‹ค.(๋„ˆ๋ฌด ๋‹น์—ฐํ•œ ์ด์•ผ๊ธฐ ๊ฐ™๊ธฐ๋„..?)

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <div>
          <Counter isFancy={true} /> 
        </div>
      ) : (
        <section>
          <Counter isFancy={false} />
        </section>
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

์œ„์˜ ์ฝ”๋“œ์—์„œ๋Š” ๋ฅผ

์™€
์œผ๋กœ ๊ฐ์‹ธ๊ณ  ์žˆ๋‹ค. ์ฆ‰, ์„œ๋กœ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋กœ ์ทจ๊ธ‰๋œ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ฒดํฌ๋ฐ•์Šค๋ฅผ ์„ ํƒํ•˜๋ฉด state๊ฐ€ ์žฌ์„ค์ •๋œ๋‹ค.

์œ„์˜ ์ด์œ ๋กœ ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ์˜ ์ •์˜๋ฅผ ์ค‘์ฒฉํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.

import { useState } from 'react';

export default function MyComponent() {
  const [counter, setCounter] = useState(0);

  function MyTextField() {
    const [text, setText] = useState('');

    return (
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
    );
  }

  return (
    <>
      <MyTextField />
      <button onClick={() => {
        setCounter(counter + 1)
      }}>Clicked {counter} times</button>
    </>
  );
}

MyTextField๊ฐ€ MyComponent ๋‚ด๋ถ€์— ์ •์˜๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— MyComponent๊ฐ€ ๋ Œ๋”๋ง๋  ๋•Œ๋งˆ๋‹ค MyTextField๊ฐ€ ์žฌ์ •์˜๋œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด state๊ฐ€ ๊ณ„์† ์žฌ์„ค์ •๋œ๋‹ค.

Resetting state at the same position

๋™์ผํ•œ ์œ„์น˜์—์„œ state๋ฅผ ์žฌ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ 2๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

๋จผ์ € ์•„๋ž˜ ์˜ˆ์ œ๋Š” Counter ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ผํ•œ ์œ„์น˜์— ์žˆ์œผ๋ฏ€๋กœ state๊ฐ€ ๋ณด์กด๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter person="Taylor" />
      ) : (
        <Counter person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

๊ทธ๋ ‡๋‹ค๋ฉด state๋ฅผ resetํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?

Option 1: Rendering a component in different positions

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA &&
        <Counter person="Taylor" />
      }
      {!isPlayerA &&
        <Counter person="Sarah" />
      }
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

์œ„์™€ ๋น„์Šทํ•œ ์˜ˆ์ œ๊ฐ™์ง€๋งŒ, Counter ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋‹ค๋ฅด๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Counter ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ผํ•œ ์œ„์น˜์— ๋ Œ๋”๋ง๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ state๊ฐ€ ์žฌ์„ค์ •๋œ๋‹ค. ์˜ˆ์ œ์™€ ๊ฐ™์ด ์œ„์น˜๊ฐ€ 2๊ฐœ์ผ ๋•Œ๋Š” ๊ดœ์ฐฎ์ง€๋งŒ, 3๊ฐœ ์ด์ƒ์ผ ๋•Œ๋Š” ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง„๋‹ค.

Option 2: Resetting state with a key

๋ชฉ๋ก์„ ๋ Œ๋”๋งํ•  ๋•Œ key๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. key๋Š” ๋ชฉ๋ก์—๋งŒ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค. key๋ฅผ ์‚ฌ์šฉํ•ด React๊ฐ€ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ React๋Š” ๋ถ€๋ชจ ๋‚ด์˜ ์ˆœ์„œ(โ€œ์ฒซ ๋ฒˆ์งธ counterโ€, โ€œ๋‘ ๋ฒˆ์งธ counterโ€)๋ฅผ ์‚ฌ์šฉํ•ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ๋ถ„ํ•œ๋‹ค. ํ•˜์ง€๋งŒ key๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๊ฒƒ์ด ์ฒซ ๋ฒˆ์งธ counter๋‚˜ ๋‘ ๋ฒˆ์งธ counter๊ฐ€ ์•„๋‹ˆ๋ผ ํŠน์ • counter(์˜ˆ: Taylor์˜ counter)์ž„์„ React์— ์•Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, ์ˆœ์„œ์— ๋Œ€ํ•ด ์ข€๋” ๋ช…์‹œ์ ์œผ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter key="Taylor" person="Taylor" />
      ) : (
        <Counter key="Sarah" person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

Counter ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์ผํ•œ ์œ„์น˜์— ์กด์žฌํ•˜์ง€๋งŒ key ๊ฐ’์„ ๋‹ค๋ฅด๊ฒŒ ์คŒ์œผ๋กœ์จ ๋™์ผํ•œ ์œ„์น˜์— ์žˆ๋”๋ผ๋„ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋กœ ์ธ์‹ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

key๋Š” ์ „์—ญ์œผ๋กœ ๊ณต์œ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ํ•ด๋‹น ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ๊ณต์œ ํ•œ๋‹ค.

Resetting a form with a key

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.id} contact={to} />
    </div>
  )
}

const contacts = [
  { id: 0, name: 'Taylor', email: 'taylor@mail.com' },
  { id: 1, name: 'Alice', email: 'alice@mail.com' },
  { id: 2, name: 'Bob', email: 'bob@mail.com' }
];

input์„ ๋‹ด๊ณ  ์žˆ๋Š” Chat ์ปดํฌ๋„ŒํŠธ์— key๋ฅผ ์ „๋‹ฌํ•จ์œผ๋กœ์จ input์˜ state๋ฅผ resetํ•  ์ˆ˜ ์žˆ๋‹ค.

Preserving state for removed components

๋งŒ์•ฝ ๊ฐ key์— ํ•ด๋‹นํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ์ด์ „ ๊ฐ’์„ ๊ธฐ์–ตํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

  • ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋˜ ๋‹ค๋ฅธ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ CSS๋กœ ์ˆจ๊ธด๋‹ค. ์ปดํฌ๋„ŒํŠธ์˜ ๊ฐฏ์ˆ˜๊ฐ€ ๋งŽ์œผ๋ฉด ์„ฑ๋Šฅ์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ๊ฐ„๋‹จํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์—…ํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค.
  • ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์— state๋ฅผ ๋Œ์–ด์˜ฌ๋ ค์„œ ๋ณด๊ด€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ œ๊ฑฐ๋˜๋”๋ผ๋„ ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ๋ณด๊ด€ํ•˜๋Š” ๊ฒƒ์€ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์ด๋ฏ€๋กœ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š๋Š”๋‹ค.(์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•)
  • React state ์™ธ์— ๋‹ค๋ฅธ ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ˆ˜๋กœ ํŽ˜์ด์ง€๋ฅผ ๋‹ซ์•„๋„ ๋ฉ”์‹œ์ง€ ์ดˆ์•ˆ์ด ์œ ์ง€๋˜๊ธฐ๋ฅผ ์›ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด localStorage๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์–ด๋–ค ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋“  ๊ฐœ๋…์ ์œผ๋กœ ๊ตฌ๋ถ„๋˜์–ด์•ผ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ผ๋ฉด key๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.