상태 관리란, 앱 상에서의 데이터를 메모리 등에 저장하고 하나 이상의 컴포넌트에서 데이터를 공유하는 것이다.
SPA에서 페이지를 로딩할 때마다 모든 데이터를 로딩한다면, 사용자 경험 측면에서 MPA를 크게 넘어서기 힘들다.
오히려 네트워크 요청 수가 많아져서 더 느려질 수도 있다.
따라서 변경이 잦은 데이터가 아니라면 데이터를 캐싱하고 재활용해야 한다.
만약 변경이 잦다면, 데이터의 변경 시점을 파악해 최적화하는 것이 좋다.
(ex: 일정 시간마다 서버에 저장, 타이핑 5초후 서버에 저장 등)
Prop Drilling
상위 컴포넌트의 데이터를 하위 컴포넌트로 전달하기 위해 props로 전달하는 형태
컴포넌트가 복잡해지면 부모와 자식 컴포넌트 간의 깊이도 커진다.
최하단의 자식 컴포넌트 데이터를 쓰기 위해 최상위 컴포넌트부터 데이터를 보내야하는 상황이 발생할 수도 있다.
이런 경우에는 Prop Drilling은 상태값의 추적과 유지보수가 힘들어진다는 단점이 있다.
이를 보완하기 위해 전역적으로 상태값을 관리할 수 있는 Context API를 활용해서 필요한 컴포넌트에서 데이터를 꺼내서 사용할 수 있다.
Action -> Dispatcher -> Store -> View 순으로 데이터가 흐름
Store는 미리 Dispatcher에 콜백 등록해 자신이 처리할 Action을 정의
Action creator는 Action을 생성해 Dispatcher로 보냄
Dispatcher는 Action을 Store로 넘김
Store는 Action에 따라 데이터 업데이트 -> 관련 View로 변경 이벤트 발생
View는 데이터를 다시 받아와 새로운 UI 생성
유저 인터랙션 발생하면 View는 Action을 발생시킴
const [<상태 객체>, <dispatch 함수>] = useReducer(<reducer 함수>, <초기 상태>, <초기 함수>)
컴포넌트에서 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(callback, [변경되는 값])
useCallback(callback, [변경되는 값]);