State 관리하기

Description: State를 깊게 이해하기 위한 목차입니다. input으로 State받기, 오래 보존하기, 공유하기, reducer사용하기 등 State에 대해 심도 있게 배웁니다.

현재 노트: KR-010.20 c
상위 분류: KR-010.20 React

#React

참조 링크
https://ko.react.dev/learn/reacting-to-input-with-state

컴포넌트의 모든 state를 한 페이지에 보여주는 페이지를 living styleguides 또는 storybooks라고한다

state의 변경을 트리거하는 인 풋 유형은 크게 사람과 컴퓨터 2가지

버튼을 누르거나, 필드를 입력하거나, 링크를 이동하는 것 등의 휴먼 인풋
네트워크 응답이 오거나, 타임아웃이 되거나, 이미지를 로딩하거나 하는 등의 컴퓨터 인풋

불필요한 state변수가 있는지 확인해봐야합니다.

state가 역설을 일으키지는 않나요? 예를 들면 isTyping과 isSubmitting이 동시에 true일 수는 없습니다. 이러한 역설은 보통 state가 충분히 제한되지 않았음을 의미합니다. 여기에는 두 boolean에 대한 네 가지 조합이 있지만 오직 유효한 state는 세 개뿐입니다. 이러한 “불가능한” state를 제거하기 위해 세 가지 값 'typing', 'submitting', 'success'을 하나의 status로 합칠 수 있습니다.>
다른 state 변수에 이미 같은 정보가 담겨있진 않나요? isEmpty와 isTyping은 동시에 true가 될 수 없습니다. 이를 각각의 state 변수로 분리하면 싱크가 맞지 않거나 버그가 발생할 위험이 있습니
다. 이 경우에는 운이 좋게도 isEmpty를 지우고 answer.length === 0으로 체크할 수 있습니다.
다른 변수를 뒤집었을 때 같은 정보를 얻을 수 있진 않나요? isError는 error !== null로도 대신 확인할 수 있기 때문에 필요하지 않습니다.

React 컴포넌트 state의 보존

React는 컴포넌트가 UI 트리에서 그 자리에 렌더링되는 한 state를 유지합니다. 만약 그것을 제거하거나 같은 자리에 다른 컴포넌트가 렌더링되면 React는 그 state를 버립니다.

지워졌다 리렌더링 될때 컴포넌트의 state를 유지하는 방법

경험상(rule of thumb) 리렌더링할 때 state를 유지하고 싶다면, 트리 구조가 “같아야” 합니다. 만약 구조가 다르다면 React가 트리에서 컴포넌트를 지울 때 state로 지우기 때문에 state가 유지되지 않습니다.

같은 컴포넌트지만 서로 다른 state를 보존할때 사용 방법

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

state가 너무 많아질경우 단일 함수 reducer로 관리하기

reducer는 state를 다루는 다른 방법입니다. 다음과 같은 세가지 단계에 걸쳐 useState에서 useReducer로 바꿀 수 있습니다.

  1. state를 설정하는 것에서 action을 dispatch 함수로 전달하는 것으로 바꾸기.
  2. reducer 함수 작성하기.
  3. 컴포넌트에서 reducer 사용하기.

dispatch에 넘겨주는 action객체

일반적인 JavaScript의 객체가 action객체로 서 dispatch의 인자로 넘겨받습니다. 무엇이와도 상관 없지만 일반적으로 발생한 일을 설명가능한 type등의 이름이 필요

reducer안에서는 switch를 사용하는게 규칙

위 코드에서 if/else 문을 사용하고 있지만 reducer 함수 안에서는 switch 문을 사용하는 게 규칙입니다. 물론 결과는 같지만, switch 문으로 작성하는 것이 한눈에 읽기 더 쉬울 수 있습니다. 이제부터 이 문서에서 다룰 예시는 아래처럼 switch 문을 사용합니다.

redcuer라고 부르는 이유? JavaScript의 reduce에서 따와서

reducer와 state의 가장 큰 차이

만약 일부 컴포넌트에서 잘못된 방식으로 state를 업데이트하는 것으로 인한 버그가 자주 발생하거나 해당 코드에 더 많은 구조를 도입하고 싶다면 reducer 사용을 권장합니다. 이때 모든 부분에 reducer를 적용하지 않아도 됩니다.
외부에서 컴포넌트 독립적으로 복잡한 구조에 대해 테스팅까지 필요할 시 reducer가 나아보임

reducer와 Context로 앱 확장하기

useState방식에서 reducer방식으로 변경하면 복잡한 State를 하나의 reducer로 관리할 수 있게 된다. 하지만 props전달방식이 사용될 경우 dispatch 함수를 props로 전달하게된다. 그래서 Context를 사용해 dispatch와 State를 전달할 수 있게 변형한다면 확장에 쉬운 앱을 만들 수 있다.

Context 조합 전 코드
App.js내에서 useReducer로 State를 관리하면 props로 dispatch를 전달함

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

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

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

  return (
    <>
      <h1>Day off in Kyoto</h1>
      <AddTask onAddTask={handleAddTask} />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

Context를 따로 파일로 추출
TaskProvider로 감싸고 children으로 넘겨주기

import { createContext, useContext, useReducer } from 'react';

const TasksContext = createContext(null);

const TasksDispatchContext = createContext(null);

export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        {children}
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

export function useTasks() {
  return useContext(TasksContext);
}

export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}

...

변경된 App.js
원래 App.js에서 관리하던 State와 reducer 그리고 아래로 내려주던 props가 전부 TasksProvider에서 관리되면서 App.js는 무엇을 보여줄지에 관해 집중할 수 있게 된다.

import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';

export default function TaskApp() {
  return (
    <TasksProvider>
      <h1>Day off in Kyoto</h1>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}