(props, state) => UI(JSX)
1. 상태 관리
1-1. 상태(state)
렌드링 결과에 영향을 주는 정보를 담은 순수 자바스크립트 객체
1-2. 상태를 잘 관리하기 위한 가이드
- 시간이 지나도 변하지 않나요? 그러면 확실히 state가 아닙니다.
- 부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아닙니다.
- 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가요? 그렇다면 절대로 state가 아닙니다!
그 외 남는 건 아마 state일 겁니다.
📚 React 공식문서 - thinking in react
내부에 존재하는 상태를 useEffect로 동기화하면 개발자가 추적하기 어려워 오류가 발생할 수 있다. useEffect로 동기화 하는 것은 피해야 한다.
📚 311p
1-3. useState vs useReducer
useState 대신 useReducer 사용을 권장하는 경우는 크게 2가지가 있따.
- 다수의 하위 필드를 포함하고 있는 복잡한 상태 로직을 다룰 때
- 다음 상태가 이전 상태에 의존적일 때
Advanced Search와 같이 쿼리를 상태로 저장해야한다고 해보자. 이런 쿼리들은 단순하지 않고 검색 날짜 범위, 키워드, 카드사 등 다양한 필드를 포함할 수 있다. 페이지네이션을 고려한다면 페이지, 사이즈 등의 필드도 추가될 수 있다.
type DateRangePreset = 'today' | 'yesterday' | 'thisWeek' | 'lastWeek' | 'thisMonth' | 'lastMonth';
enum CardSet = {
VISA = 'VISA',
MASTER = 'MASTER',
AMEX = 'AMEX',
JCB = 'JCB',
UNIONPAY = 'UNIONPAY',
}
interface SearchFilter {
keyword: string;
dateRange: DateRangePreset;
cardSet: CardSet[];
// 이외 기타 필터링 옵션
}
interface State {
filter: SearchFilter;
page: number;
size: number;
}
이러한 데이터 구조를
useState
로 다루면 상태를 업데이트할 때마다 잠재적인 오류 가능성이 높아진다. 이런 복잡한 상태 로직을 다룰 때는useReducer
를 사용하는 것이 좋다.
useReducer
는 '무엇을 변경할지'와 '어떻게 변경할지'를 분리하여dispatch
를 통해 어떤 작업을 할지를 액션으로 넘기고reducer
함수 내에서 상태를 업데이트하는 방식을 정의한다
📚 314p
// Action 정의
type Action =
| { payload: SearchFilter; type: "filter" }
| { payload: number; type: "navigate" }
| { payload: number; type: "resize" };
// Reducer 정의
const reducer: React.Reducer<State, Action> = (state, action) => {
switch (action.type) {
case "filter":
return { filter: action.payload, page: 0, size: state.size };
case "navigate":
return { ...state, page: action.payload };
case "resize":
return { ...state, size: action.payload };
default:
return state;
}
};
// useReducer 사용
const [state, dispatch] = useReducer(reducer, initialState);
// dispatch 사용
dispatch({
type: "filter",
payload: { keyword: "react", dateRange: "today", cardSet: ["VISA"] },
});
dispatch({ type: "navigate", payload: 1 });
dispatch({ type: "resize", payload: 10 });
이외에도 boolean 상태를 토글하는 액션만 사용하는 경우 useState
대신 useReducer
를 사용하곤 한다.
const [fold, setFold] = useReducer((fold) => !fold, false);
1-4. Context API
Context API를 이용하여 전역 상태를 관리하는 것은 대규모 애플리케이션이나 성능이 중요한 애플리케이션에서 권장되진 않는다.
- 컨텍스트 프로바이더의 props로 주입된 값이나 참조가 변경될 때마다 해당 컨텍스트를 구독하고 있는 모든 컴포넌트가 리렌더링
컨텍스트를 생성할 때 관심사를 잘 분리해서 구성하면 리렌더링을 최소화할 수 있지만, 애플리케이션이 커지고 전역 상태가 많아질수록 불필요한 리렌더링과 상태의 복잡도가 증가한다.
Bad Case 예시 하나의 컨텍스트에 여러 관심사 포함:
const AppStateContext = React.createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("light");
const [products, setProducts] = useState([]);
return (
<AppStateContext.Provider
value={{ user, setUser, theme, setTheme, products, setProducts }}
>
{/* Application Components */}
</AppStateContext.Provider>
);
}
이 경우, 사용자 정보, 테마, 제품 데이터 등 서로 다른 관심사의 상태들이 하나의 컨텍스트에 포함되어 있습니다. 따라서 테마를 변경할 때 사용자 정보와 제품 정보를 포함한 모든 컴포넌트가 리렌더링 될 수 있습니다.
const UserContext = React.createContext();
const ThemeContext = React.createContext();
const ProductContext = React.createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState("light");
const [products, setProducts] = useState([]);
return (
<UserContext.Provider value={{ user, setUser }}>
<ThemeContext.Provider value={{ theme, setTheme }}>
<ProductContext.Provider value={{ products, setProducts }}>
{/* Application Components */}
</ProductContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>
);
}