React 공식 문서 정리 - 3.3

김석·2023년 10월 5일
0

React

목록 보기
13/14

Extracting State Logic into a Reducer

  1. 배경
    • 여러 event handler에 많은 state 업데이트가 분산된 경우의 복잡함을 줄이기 위해 등장.
    • state 업데이트 로직을 component 밖의, reducer라는 하나의 function으로 분리함.

useState에서 useReducer로 변경

  1. setState를 dispatch action으로 변경
  2. Reducer function 작성
  3. Component에 reducer 사용

1. setState를 dispatch action으로 변경

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

위 코드의 모든 setTask를 제거. 남는 것은,

  • handleAddTask(text): Add 눌렀을 경우 불림
  • handleChangeTask(task) is called when the user toggles a task or presses “Save”.
  • handleDeleteTask(taskId) is called when the user presses “Delete”.

대신에, 방금 유저가 한 action을 전달한다.

  • dispatch만 있으면, 어디에서든 state를 제어할 수 있음.
  • dispatch 함수로 전달하는 객체를 action이라고 함.
  • type은 관습적으로 쓰는 것.
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,
  });
}

2. Reducer function 작성

  • 이제 reducer function에서 state 로직을 작성하면 됨.
  • state와 action을 매개변수로 받는다. 예시에서는 tasks가 state.
  • reducer에서 return한 것이 next state가 된다.
  • reducer function이 state를 argument로 받기 때문에, reducer function을 component 밖에 작성할 수 있다.
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);
    }
  }
}

3. Component에 reducer 사용

  • tasks를 state로 설정하는데, useState가 아닌 useReducer를 사용.
  • setState의 역할을 dispatch가 함.
  • event handler에서는 취할 action을, dispatch 함수를 통해 전달함.
  • 그 action에 따라서 실제로 state를 제어할 logic은, tasksReducer 함수에 미리 정의해야 함.
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

useState vs useReducer

  • 프로젝트가 복잡해질수록, reducer를 사용해야 readability, debugging, testing이 용이하다.
  • 프로젝트가 간단하면 useState를 써도 무방하다.
  • 두 개를 같이 써도 무방하다.

reducer 잘 작성하는 법

  • pure하게 작성하기.
  • 각각의 action은 하나의 user interaction에 대응해야 함.

Passing Data Deeply with Context

부모의 state를 바로 아래 자식이 아니라, 저~멀리 떨어진 자식에게 필요한 경우?

  • state와 set함수(reducer를 사용한다면 dispatch)를 사용한 콜백함수를 prop으로 전달..전달..전달해야 함.
  • 이럴 때, Context를 이용하면 부모는 하위 트리의 모든 자식에게 state를 제공할 수 있음.

Context: props 전달의 대안

  • 부모 Component가 모든 자식에게 state를 전달할 수 있게 해줌.
  1. 어딘가에서 Context를 create.
export const LevelContext = createContext(1);
  1. 자식 어딘가에서 use Context.
const level = useContext(LevelContext);
  • 나는 LevelContext라는 Context에서 정보를 읽고 싶다는 뜻.
  1. context를 provide하는 부모 생성.
export default function Section({ level, children }) {
  return (
    <section className="section">
      <LevelContext.Provider value={level}>
        {children}
      </LevelContext.Provider>
    </section>
  );
}
  • LevelContext.Provider로 묶어줌.
  • value에 level 값을 넣어줌.
  • 이 Section component의 자식들이 context를 요청하면, 이 level에 해당하는 LevelContext를 가진다.
  • 자식 입장에서 가장 가까운 LevelContext Provider를 찾아서 거기에서 제공하는 값을 읽어옴.
  • 부모에서 지정한 Context는, 부모와 자식 사이의 component가 다른 값으로 Context를 지정하지 않는 한, 자식까지의 거리에 상관 없이 전달됨.
  • CSS에서 div color:blue 처럼, 중간에 어디선가 color:green 하지 않는 이상 색깔은 하위 요소로 상속되는 것과 비슷함.

Context는 언제 사용하는가?

  • props를 몇 단계 거쳐 전달해야 한다고 무조건 context를 사용해야만 하는 것은 아님.
  1. props를 passing하는 것으로 시작하기.
    • 여러 개의 data를 전달하는 경우는 자주 존재함.
    • 진부하지만, 각 componenet가 어떤 데이터를 사용하는지 명확하게 알 수 있는 방법임.
  2. 단계를 거쳐 prop을 거치는 상황을 해결하는 방법?
    1. props로 전달하는 그 부모 component에서 자식 componenet를 extract.
    2. children으로 그 componenet를 데리고 와서 props로 전달.
function Layout({ posts }) {
  return (
    <div>
      <Header />
      <Main posts={posts} />
    </div>
  );
}

function Main({ posts }) {
  return (
    <div>
      <Posts posts={posts} />
    </div>
  );
}

위 코드는 Posts component에서 쓸 posts를 props로 내리전달하고 있음.

function Layout({ children }) {
  return (
    <div>
      <Header />
      {children}
    </div>
  );
}

function Main({ children }) {
  return (
    <div>
      {children}
    </div>
  );
}

이렇게 children을 사용하고,

<Layout>
  <Main>
    <Posts posts={somePostsData} />
  </Main>
</Layout>
  
이렇게 전달.

이 방법들이 잘 적용되지 않을 때, context 사용을 고려하자.

  • context는 정적인 값이 아니고, 바뀔 수 있음.
  • 따라서 state와 같이 쓰일 수 있음.

출처

https://react.dev/learn/extracting-state-logic-into-a-reducer
https://react.dev/learn/passing-data-deeply-with-context

profile
handsome

0개의 댓글