React 상태 관리

heejung·2022년 6월 29일
0

React

목록 보기
11/13

상태 관리란, 앱 상에서의 데이터를 메모리 등에 저장하고 하나 이상의 컴포넌트에서 데이터를 공유하는 것이다.

MPA와 SPA에서의 상태 관리


MPA

  • 서버의 데이터를 이용해 페이지를 렌더링
  • 클라이언트 데이터와 서버의 데이터의 차이가 크지 않음

SPA

  • 자체적으로 데이터를 가짐
  • 서버와 동기화가 필요한 데이터만 처리, 그 외에는 클라이언트만의 데이터로 유지


상태 관리 기술의 도입


  • 앱이 사용하는 데이터가 많아지는 경우
  • 유저와의 인터랙션 시 임시로 저장하는 데이터가 많아지는 경우

장점

  • 높은 품질의 코드를 작성하는 데 유리함
  • 성능 최적화, 네트워크 최적화 등에 유리함
  • 데이터 관리의 고도화 (ex: localStorage를 활용한 persist state)

단점

  • Boilerpalte 문제
  • 파악해야할 로직과 레이어가 많아짐
  • 잘못 사용할 경우, 앱의 복잡도만 높이거나 성능을 악화시킬 수 있음
    (ex: global state의 잘못된 활용으로 앱 전체 리렌더링 발생 등)

상태 관리 기술이 해결하는 문제들


데이터 캐싱과 재활용

SPA에서 페이지를 로딩할 때마다 모든 데이터를 로딩한다면, 사용자 경험 측면에서 MPA를 크게 넘어서기 힘들다.
오히려 네트워크 요청 수가 많아져서 더 느려질 수도 있다.
따라서 변경이 잦은 데이터가 아니라면 데이터를 캐싱하고 재활용해야 한다.
만약 변경이 잦다면, 데이터의 변경 시점을 파악해 최적화하는 것이 좋다.
(ex: 일정 시간마다 서버에 저장, 타이핑 5초후 서버에 저장 등)

Prop Drilling

Prop Drilling
상위 컴포넌트의 데이터를 하위 컴포넌트로 전달하기 위해 props로 전달하는 형태

컴포넌트가 복잡해지면 부모와 자식 컴포넌트 간의 깊이도 커진다.
최하단의 자식 컴포넌트 데이터를 쓰기 위해 최상위 컴포넌트부터 데이터를 보내야하는 상황이 발생할 수도 있다.
이런 경우에는 Prop Drilling은 상태값의 추적과 유지보수가 힘들어진다는 단점이 있다.

이를 보완하기 위해 전역적으로 상태값을 관리할 수 있는 Context API를 활용해서 필요한 컴포넌트에서 데이터를 꺼내서 사용할 수 있다.

Flux 패턴


  • 2014년에 페이스북에서 제안한 웹 애플리케이션 아키텍처 패턴
  • 데이터의 업데이트와 UI 반영을 단순화
  • 하나의 유저 인터랙션 당 하나의 업데이트만 생성
  • 데이터와 업데이트가 한 방향으로 흐름 -> UI의 업데이트를 예측하기 쉬움

  1. Action -> Dispatcher -> Store -> View 순으로 데이터가 흐름

  2. Store는 미리 Dispatcher에 콜백 등록해 자신이 처리할 Action을 정의

  3. Action creator는 Action을 생성해 Dispatcher로 보냄

  4. Dispatcher는 Action을 Store로 넘김

  5. Store는 Action에 따라 데이터 업데이트 -> 관련 View로 변경 이벤트 발생

  6. View는 데이터를 다시 받아와 새로운 UI 생성

  7. 유저 인터랙션 발생하면 View는 Action을 발생시킴


상태 관리에 사용되는 Hooks


useState

  • 단순한 하나의 상태를 관리하기에 적합함 (소규모)
  • state 변경 시, state를 사용하는 컴포넌트 리렌더링
  • useEffect와 함께 state에 반응하는 hook을 구축

useRef

  • 상태가 변경되어도 리렌더링하지 않는 상태를 정의함
    (ex: setTimeout의 timerId 저장)
  • 리렌더링을 최소화하는 상태 관리에 사용됨 (ex: Dynamic Form)

useContext

  • 컴포넌트와 컴포넌트 간의 상태를 공유할 때 사용
  • 부분적인 컴포넌트들의 상태 관리, 전체 앱의 상태 관리 모두 구현
  • Provider 단에서 상태 정의 -> 상태를 사용하는 컴포넌트에서 useContext로 바로 상태를 가져와 사용함
  • useReducer와 함께 복잡한 상태와 변경 로직을 두 개 이상의 컴포넌트에서 활용하도록 구현 가능
  • Prop Drilling 방지 => 컴포넌트 간 결합도 낮춤

useReducer

  • 복잡한 여러 개의 상태를 한꺼번에 관리 or 어떤 상태에서 여러가지 처리를 할 때 유용함 (대규모)
  • 별도의 라이브러리 없이 flux 패턴에 기반한 상태 관리 구현
const [<상태 객체>, <dispatch 함수>] = useReducer(<reducer 함수>, <초기 상태>, <초기 함수>)
  • reducer 함수 : 현재 상태 객체와 수행할 행동 객체를 인자로 받아 새로운 상태 객체를 반환
  • dispatch 함수 : 컴포넌트 내에서 상태 변경을 일으키기 위해 사용되며, 인자로 reducer 함수에 넘길 행동 객체를 받음
    (행동 객체 => type이나 해당 행동과 관련된 데이터를 담고 있음)

컴포넌트에서 dispatch 함수에서 넘겨준 행동을 하면 reducer 함수가 이 행동에 따라 상태를 변경해준다.

<예시코드>

숫자의 증감을 출력하는 카운터 예제이다.

import React, { useReducer } from 'react'
import './App.css';

// useReducer()에 넘겨줄 초기 state
const initialState = {
    count: 0
};

// useReducer()에 넘겨줄 reducer 함수
// 현재 state와 수행할 action을 인자로 받음
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function App() {
  // 1. useReducer() 호출
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <>
      Count: {state.count}
	  // 2. dispatch로 reducer 함수에 type 프로퍼티를 가진 action 객체를 넘겨줌
	  // => state 변경
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
export default App;

useMemo

useMemo(callback, [변경되는 값])
  • 메모이제이션된 값을 반환
  • 반복되는 계산에서 같은 값을 사용해야할 때, 이전에 계산해두었던 값을 사용하여 실행 속도를 높임

useCallback

useCallback(callback, [변경되는 값]);
  • useMemo와 동일하게 메모이제이션된 값을 반환
  • useMemo는 숫자, 문자열, 객체 등의 일반적인 값에 사용, useCallback()은 함수에 사용
profile
프론트엔드 공부 기록

0개의 댓글