Frontend (React)

React 상태관리

아직개구리 2024. 2. 11. 16:55
에디터를 만들다가 상태관리를 효율적으로 할 필요를 느껴서 React 공식 문서를 찾아봤는데, 너무 잘 정리가 되어 있어서 조금 정리를 해 보았다. 

출처 :  https://react.dev/learn/managing-state 

 

Managing State – React

The library for web and native user interfaces

react.dev

Sharing State Between Components

  • 두개의 components 에서 하나의 스테이트를 공유하기 위해서는 공통 부모 component에 state 를 추가하고 event handler와 함께 그것을 pass it down 하면 된다. 
  • For each unique piece of state, you will choose the component that “owns” it. 이 원칙은 single source of truth 로도 알려져 있다. 모든 상태가 하나의 place에 있다는 것을 의미하는 게 아니고, 각각의 상태에 대해서 어떤 특정 component가 이 정보를 들고 있다는 것을 의미한다.

Preserving and Resetting State

  • react 는 렌더 트리를 구성한다. component에 state를 부여하면, 그 state는 그 component 안에서 살고 있다고 생각할 수 있다. 하지만, state는 React 안에 있는것이다. React는 들고있는 각각의 state를 컴포넌트가 render tree에 어디에 있는지에 따라 correct component와 연결짓는다. 
  • react 는 component의 state 를 UI tree에서 그 component의 position에 render 되어 있을 때까지 보존한다. component가 제거되거나 다른 component가 똑같은 position에 rendered되었을 때 react는 그 state를 버리게 된다.
  • Same component at the same position인 경우, state가 보존된다. Different components at the same position 은 reset한다.
  • re-render할 때 state를 보존하고 싶다면, tree의 structure를 match up 시켜야한다. 구조가 다다면 react는 tree에서 component를 제거할 때 state도 파괴시킨다. 
  • You should not nest component function definitions.
    • function component가 안쪽에 하나 nested되어 있으면, 바깥쪽 component가 매번 render될 때마다 안쪽의 function component도 새롭게 생성되는데, 이건 다른 것이다. 같은 포지션의 다른 component처럼 작동한다. 그렇기 떄문에 이렇게 하면 bug나 performance problem을 야기할 수 있다.
  • Resetting state at the same position
    • key 를 설정하거나 다른 위치에 설정하거나 하는 방법으로 같은 위치지만, state를 리셋 할 수 있다.
    • 똑같은 위치여도, key를 바꿔줌으로써 state를 key가 바뀔 때마다 바꿔줄 수 있다.

Extracting State Logic into a Reducer

  • state를 setting하면서 React에게 무엇을 해야하는지 알려주지 않고, handler에서 dispatching actions를 함으로써 유저가 방금 무엇을 했는지 전달하는 방식이다.
  • instead of “setting tasks” via an event handler, you’re dispatching an “added/changed/deleted a task” action. dispatch라는 함수로 전달하는 object를 action이라고 부른다. 일반적으로 이 action에는 무엇이 일어났는지에 대한 최소한의 정보가 담겨있다.
  • switch statement 를 쓰는게 convention이다. case 마다 {} curly braces를 사용해서 다른 case 아래의 내용들과 충돌하지 않게 한다. case 는 return을 한다.

Passing Data Deeply with Context

  • Context는 parent component에서 어떤 information 이 그 아래에 있는 tree에 있는 어떤 component에서도 사용가능한것이다.
  • context 를 사용하면 props drilling을 방지할 수 있다.
import { LevelContext } from './LevelContext.js';

export default function Section({ level, children }) {
  return (
    <section className="section">
      <LevelContext.Provider value={level}>
        {children}
      </LevelContext.Provider>
    </section>
  );
} 
  • 이렇게 위처럼 사용을 하게 되면, Nearest provider에 있는 value를 사용하게 된다.
  • Context는 component가 adapt to their surroundings 하도록 하고, rendered된 위치에 따라서 다르게 display한다.
  • Context가 작동하는 방식은 CSS property inheritance를 떠올리게 한다. CSS에서 div 에 color: blue라고 설정해놓으면 어떤 돔 노드에 있던 간에 color가 inherit된다. 중간에 override하기 전까지. 마찬가지로 위에서 오는 context를 바꾸고 싶으면 다른 값을 가지는 provider로 children을 감싸는 방법밖에 없다.

Use Cases for context

  • theming
  • current account
  • Routing : routing solution들은 보통 내부적으로 current route 를 알고 있기 위해 사용한다.
  • Managing state : app이 커지게 되면, 결국에 많은 state가 top of your app 에 가까워지게 된다. 많은 distant components 들이 그걸 바꾸고 싶어하게 되어서, reducer와 context를 같이 써서 보통 complex state를 관리하고, 귀찮은 것 없이 distant components 에게 state를 전달하게 된다.

Scaling Up with Reducer and Context

Reducer는 component의 state update logic를 통합할 수 있도록 해주고, context는다른 깊게 위치해 있는 component 에 정보를 전달시킬 수 있도록 해준다. 이 두개를 합치면 complex screen의 state를 잘 관리할 수 있게 된다.

 

Combining a reducer with context

  • Task state와 dispatch function은 지금 가장 top-level component에서 사용가능하다. current state와 그것을 고치기 위한 eventhandler들을 pass down 해야한다.
  • 이때, context를 사용한다면, 가장 위의 component가 아닌 그 아래에 있는 component에서도 반복적인 prop drilling을 하지 않으면서, task와 dispatch action들을 전달할 수 있다.

 

예시에서 context 두개를 생성한다. TasksContext는 current list of tasks를 뜻하고 TasksDispatchContext는 component가 action들을 dispatch할 수 있도록 하는 function을 제공한다. UseReducer()를 통해서 얻은 tasks, dispatch 를 가지고 context에 value로 입력하면, 아래의 component들에서 사용가능해진다.

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
  // ...
  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        ...
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

State는 여전히 top-level component에 “lives” 하고 있고, useReducer를 사용해서 관리되고 있지만, context를 사용해서 다른 component들에서 사용가능해질 수 있다는 것이다.

 

Moving all wiring into a single file

  • Context file하나에 관련된 것들을 모을 수 있겠다. 그리고 context를 가져오는 함수를 만들어서 사용할 수 있다. 이렇게 사용하면 component를 clean 하고 uncluttered하게 사용할 수 있고, 어디서 데이터를 얻어오는지보다 어떤 것을 display하는지에 대해 집중할 수 있다.
export function useTasks() {
  return useContext(TasksContext);
}

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