본문으로 건너뛰기

"state" 태그로 연결된 1개 게시물개의 게시물이 있습니다.

모든 태그 보기

· 약 6분
박윤국

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