React/공식 문서

useReducer

favor 2026. 5. 21. 15:15

State를 보존하고 초기화하기

각 컴포넌트는 독립된 State를 가짐
React는 UI 트리에서의 위치를 통해 각 State가 어떤 컴포넌트에 속하는지 추적
리렌더링마다 언제 State를 보존하고 초기화할지 컨트롤 가능

 

✔️ State는 렌더트리의 위치에 연결됨

React는 UI 안에 있는 컴포넌트 구조로 렌더 트리를 만듦

 

컴포넌트 안에 State를 선언

  • State는 해당 컴포넌트 내부에 종속되어 존재 (X)
  • State는 React 엔진 내부(메모리 장부)에 존재 (O)

 

React는 컴포넌트가 UI 트리에 있는 위치를 이용해 React가 가지고 있는 각 State를 알맞은 컴포넌트와 연결

 

// App.js

import { useState } from 'react';

export default function App() {
  const counter = <Counter />;
  return (
    <div>
      {counter}
      {counter}
    </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>
  );
}

 

이 둘은 트리에서 자기 고유의 위치에 렌더링되어 있음 == 분리되어 있는 카운터

 

React에서 화면의 각 컴포넌트는 완전히 분리된 State를 가짐

ex) 두 Counter 컴포넌트를 나란히 렌더링하면 그들은 자신만의 독립된 score과 hover State를 가지게 됨

 

// App.js

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>
  );
}

^ 두 번째 카운터를 렌더링하지 않을 때 그 State가 완전히 사라짐

--> React가 컴포넌트를 제거할 때 그 State도 같이 제거하기 때문

 

React는 컴포넌트가 UI 트리에서 그 자리에 렌더링되는 한 State를 유지함

만약 그것을 제거하거나 같은 자리에 다른 컴포넌트가 렌더링되면 그 State를 버림 

 

✔️ 같은 자리의 같은 컴포넌트는 State를 보존 

// App.js

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는 초기화되지 X

isFancy가 true이든 false이든 <Counter />는 같은 자리에 존재

ㄴ root App 컴포넌트가 반환한 div의 첫 번째 자식으로

 

첫 번째 자리의 같은 컴포넌트이기 때문에 React의 관점에서는 같은 카운터

 

✔️ 같은 위치의 다른 컴포넌트는 State를 초기화

// App.js

import { useState } from 'react';

export default function App() {
  const [isPaused, setIsPaused] = useState(false);
  return (
    <div>
      {isPaused ? (
        <p>See you later!</p>
      ) : (
        <Counter />
      )}
      <label>
        <input
          type="checkbox"
          checked={isPaused}
          onChange={e => {
            setIsPaused(e.target.checked)
          }}
        />
        Take a break
      </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>
  );
}

^ 체크박스를 선택하면 <Couter>가 <p>로 교체됨

 

같은 자리의 다른 컴포넌트 타입으로 바꿈

처음에는 <div>가 Counter를 갖고 있지만, p로 바꾸면 React는 UI 트리에서 Counter와 그 State를 제거 

 

같은 위치에 다른 컴포넌트를 렌더링할 때 컴포넌트는 그의 전체 서브 트리의 State를 초기화함 

 

// App.js

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>
  );
}

^ Counter를 렌더링하지만, div의 첫 번째 자식이 section에서 div로 바뀜

ㄴ 자식 section이 DOM에서 제거되었을 때, 그 아래 전체 트리도 함께 제거됨 

 

 

리렌더링할 때 State를 유지하고 싶다면, 트리 구조가 같아야 함

만약 구조가 다르다면 React가 트리에서 컴포넌트를 지울 때 State를 지우기 때문에 State가 유지되지 않음

 

✔️ 같은 위치에서 State를 초기화하기 

기본적으로 React는 컴포넌트가 같은 위치를 유지하면 State를 유지 

 

// App.js

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>
  );
}

^ 선수를 바꿀 때 점수가 유지됨

ㄴ 두 Counter가 같은 위치에 나타나기 때문에 React는 그들을 person props가 변경된 같은 Counter로 봄

 

개념적으로 App에는 두 개의 분리된 카운터가 있어야 함

그들은 UI에 같은 위치에 나타나지만, 하나는 Taylor의 카운터이고 다른 하나는 Sarah의 카운터

 

둘을 바꿀 때 각 State를 초기화하기 위한 두 가지 방법

  1. 다른 위치에 컴포넌트를 렌더링하기
  2. 각 컴포넌트에 key로 명시적인 식별자를 제공하기

 

✔️ 선택지 1: 다른 위치에 컴포넌트를 렌더링하기

두 Counter가 독립적이기를 원한다면 둘을 다른 위치에 렌더링할 수 O

 

// App.js

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>
  );
}

 

  • 처음에는 isPlayerA가 true
    • 첫 번째 자리에 Counter가 있고, 두 번째 자리는 비어있음 
  • 'Next player' 클릭
    • 첫 번째 자리는 비워지고, 두 번째 자리에 Counter가 옴 

 

* 해당 방법은 같은 자리에 적은 수의 독립된 컴포넌트만을 가지고 있을 때 편리함

 

✔️ 선택지 2: key를 이용해 State를 초기화하기

React가 컴포넌트를 구별할 수 있도록 key를 사용할 수도 있음

기본적으로 React는 컴포넌트를 구별하기 위해 부모 안에서의 순서('첫 번째 카운터', '두 번째 카운터')를 이용

그러나, key를 이용하면 React에게 단지 첫 번째, 두 번째 카운터가 아니라 특정한 카운터라고 정해줄 수 있음

ex) Taylor의 카운터, Sarah의 카운터

 

// App.js

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 />는 JSX에서 같은 위치에 나타나지만, State를 공유하지는 않음

ㄴ 다른 key를 주었기 때문

 

key를 명시하면 React는 부모 내에서의 순서 대신에 key 자체를 위치의 일부로 사용

 

* key는 전역적으로 유일하지 않고, 오직 부모 안에서만 자리를 명시함

 

✔️ key를 이용해 폼을 초기화하기

// App.js

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' }
];
// ContactList.js

export default function ContactList({
  selectedContact,
  contacts,
  onSelect
}) {
  return (
    <section className="contact-list">
      <ul>
        {contacts.map(contact =>
          <li key={contact.id}>
            <button onClick={() => {
              onSelect(contact);
            }}>
              {contact.name}
            </button>
          </li>
        )}
      </ul>
    </section>
  );
}
// Chat.js

import { useState } from 'react';

export default function Chat({ contact }) {
  const [text, setText] = useState('');
  return (
    <section className="chat">
      <textarea
        value={text}
        placeholder={'Chat to ' + contact.name}
        onChange={e => setText(e.target.value)}
      />
      <br />
      <button>Send to {contact.email}</button>
    </section>
  );
}

 

 

 


State 로직을 Reducer로 작성하기

한 컴포넌트에서 State 업데이트가 여러 이벤트 핸들러로 분산되는 경우 O
--> 컴포넌트를 관리하기 어려워짐
문제 해결을 위해 State를 업데이트하는 모든 로직을 Reducer를 사용해 컴포넌트 외부의 단일 함수로 통합해 관리할 수 O

 

✔️ Reducer를 사용하여 State 로직 통합하기

컴포넌트가 복잡해지면 컴포넌트의 State가 업데이트되는 다양한 경우를 한눈에 파악하기 어려워짐

 

// App.js

import { useState } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

export default function TaskApp() {
  const [tasks, setTasks] = useState(initialTasks);

  function handleAddTask(text) {
    setTasks([...tasks, {
      id: nextId++,
      text: text,
      done: false
    }]);
  }

  function handleChangeTask(task) {
    setTasks(tasks.map(t => {
      if (t.id === task.id) {
        return task;
      } else {
        return t;
      }
    }));
  }

  function handleDeleteTask(taskId) {
    setTasks(
      tasks.filter(t => t.id !== taskId)
    );
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false },
];

^ 각 이벤트 핸들러는 State를 업데이트하기 위해 setTasks를 호출

--> 컴포넌트가 커질수록 그 안에서 State를 다루는 로직의 양도 늘어나게 됨

복잡성은 줄이고 접근성을 높이기 위해, 컴포넌트 내부의 State 로직을 컴포넌트 외부의 Reducer라는 단일 함수로 옮길 수 있음 

 

Reducer는 State를 다루는 다른 방법

useState에서 useReducer로 바꾸는 단계

  1. State를 설정하는 방식에서 Action을 Dispatch하는 방식으로 전환하기
  2. Reducer 함수 작성하기
  3. 컴포넌트에서 Reducer 사용하기

 

✔️ 1단계: State를 설정하는 방식에서 Action을 Dispatch하는 방식으로 전환하기

function handleAddTask(text) {
  setTasks([...tasks, {
    id: nextId++,
    text: text,
    done: false
  }]);
}

function handleChangeTask(task) {
  setTasks(tasks.map(t => {
    if (t.id === task.id) {
      return task;
    } else {
      return t;
    }
  }));
}

function handleDeleteTask(taskId) {
  setTasks(
    tasks.filter(t => t.id !== taskId)
  );
}

^ 현재 이벤트 핸들러는 State를 설정함으로써 무엇을 할 것인지를 명시

 

Reducer를 사용한 State 관리는 State를 직접 설정하는 것과 약간 다름

State를 설정하여 React에게 무엇을 할지를 지시하는 대신, 이벤트 핸들러에서 Action을 전달하여 사용자가 방금 한 일을 지정

 

function handleAddTask(text) {
  dispatch({
    type: 'added',
    id: nextId++,
    text: text,
  });
}

function handleChangeTask(task) {
  dispatch({
    type: 'changed',
    task: task
  });
}

function handleDeleteTask(taskId) {
  dispatch({
    type: 'deleted',
    id: taskId
  });
}

 

function handleDeleteTask(taskId) {
  dispatch(
    // "Action" 객체:
    {
      type: 'deleted',
      id: taskId
    }
  );
}

^ dispatch 함수에 넣어준 객체를 Action이라고 함 

ㄴ 일반적인 자바스크립트 객체 (어떤 상황이 발생하는지에 대한 최소한의 정보를 담고 있어야 함)

 

✔️ 2단계: Reducer 함수 작성하기

Reducer 함수는 State에 대한 로직을 넣는 곳

현재의 State 값과 Action 객체, 두 개의 인자를 받고 다음 State 값을 반환

 

function yourReducer(state, action) {
  // React가 설정하게될 다음 State 값을 반환합니다.
}

 

React는 Reducer에서 반환한 값을 State에 설정

 

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

^ State 설정과 관련 모든 로직을 Reducer 함수로 마이그레이션한 코드

 

✔️ 3단계: 컴포넌트에서 Reducer 사용하기

import { useReducer } from 'react';

^ React에서 useReducer Hook을 불러오기

 

const [tasks, setTasks] = useState(initialTasks);
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

^ useState를 useReducer로 바꾸기 

 

useReducer Hook은 두 개의 인자를 넘겨받음

  1. Reducer 함수
  2. 초기 State 값

 

반환하는 값

  1. State를 담을 수 있는 값
  2. Dispatch 함수 (사용자의 Action을 Reducer 함수에게 전달하게 될)

 

// App.js

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false }
];

 

✔️ useState와 useReducer 비교하기

  • 코드 크기
    • 일반적으로 useState를 사용하면 미리 작성해야 하는 코드가 줄어듬
    • useReducer를 사용하면 Reducer 함수, Action을 전달하는 부분 둘다 작성해야 함
    • 여러 이벤트 핸들러에서 비슷한 방식으로 State를 업데이트하는 경우, useReducer를 사용하면 코드의 양을 줄일 수 있음
  • 가독성
    • useState로 간단한 State를 업데이트하는 경우에는 가독성이 좋은 편
    • 그렇지만 더 복잡한 구조의 State를 다루게 되면 컴포넌트의 코드 양이 더 많아져 한눈에 읽기 어려워질 수 있음
    • 이 경우 useReducer를 사용하면 업데이트 로직이 어떻게 동작하는지와 이벤트 핸들러를 통해서 무엇이 발생했는지 구현한 부분을 명확하게 구분 가능 
  • 디버깅
    • useState를 사용하면 버그를 발견했을 때 왜, 어디서 State가 잘못 설정됐는지 찾기 어려울 수 있음
    • useReducer를 사용하면 콘솔 로그를 Reducer에 추가하여 State가 업데이트되는 모든 부분과 왜 해당 버그가 발생했는지 확인 가능
    • 그렇지만 useState를 사용하는 경우보다 더 많은 코드를 단계별로 실행해서 디버깅해야 하는 점이 있음
  • 테스팅
    • Reducer는 컴포넌트에 의존하지 않는 순수 함수 --> Reducer를 독립적으로 분리해서 내보내거나 테스트할 수 있음

 

✔️ Reducer 잘 작성하기

  • Reducer는 반드시 순수해야 함
    • Reducer는 렌더링 중에 실행됨
    • 입력 값이 같다면 결과 값도 같아야 함
    • 요청을 보내거나 timeout을 스케줄링하거나 사이드 이펙트(컴포넌트 외부에 영향을 미치는 작업)를 수행해서는 안 됨
    • Reducer는 객체와 배열을 변경하지 않고 업데이트해야 함
  • 각 Action은 데이터 안에서 여러 변경들이 있더라도 하나의 사용자 상호작용을 설명해야 함
    • 사용자가 Reducer가 관리하는 5개의 필드가 있는 양식에서 '재설정'을 누른 경우, 5개의 개별 set_field Action보다는 하나의 reset_from Action을 전송하는 것이 더 합리적

 

✔️ Immer로 간결한 Reducer 작성하기

일반적인 State에서 객체와 배열을 업데이트하는 것처럼, Immer 라이브러리를 사용하면 Reducer를 더 간결하게 작성 가능

// package.json

{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}
// App.js

import { useImmerReducer } from 'use-immer';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

function tasksReducer(draft, action) {
  switch (action.type) {
    case 'added': {
      draft.push({
        id: action.id,
        text: action.text,
        done: false
      });
      break;
    }
    case 'changed': {
      const index = draft.findIndex(t =>
        t.id === action.task.id
      );
      draft[index] = action.task;
      break;
    }
    case 'deleted': {
      return draft.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

export default function TaskApp() {
  const [tasks, dispatch] = useImmerReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false },
];

^ useImmerReducer를 사용하여 push 또는 arr[i] = 로 값을 할당함으로써 State를 변경 

'React > 공식 문서' 카테고리의 다른 글

Context API  (0) 2026.05.27
상태 구조 설계와 공유  (0) 2026.05.07
불변 업데이트 패턴  (0) 2026.04.28
렌더링과 State 업데이트  (0) 2026.04.08
이벤트 처리와 State  (0) 2026.04.02