🗳️Code Style

1. Context 사용 방법

use-context-selector 를 사용하여 구현합니다.

custom-hooks 를 만들고, createContextSelector 함수에 hook 을 인자로 전달해 주면, hook 의 return 값에 해당하는 컨텍스트를 사용할 수 있는 useContext hook 과, Provider 를 제공해 줍니다.

import { createContextSelector } from '@/utils/react/create-context-selector';

const useCount = () => {
  const [count, setCount] = useState(0)
  return { count, setCount };
};

export const {
  Provider: CountProvider,
  useContext: useCountContext,
} = createContextSelector(useCount);

왜 전역 상태 라이브러리 대신 Context를 사용하나요?

전역 상태는 편리할 수 있지만, 프로그래밍 관점에서 피해야할 '달콤한' 요소입니다. 전역 상태가 어디서든 수정 가능하다면, 프로그램의 어느 곳에서 상태를 변경할 수 있는 가능성이 높아져 복잡성이 증가할 수 있습니다. 이러한 이유로, 우리는 Context를 활용하여 상태를 지역적으로 관리하면서도 편리하게 사용할 수 있는 패턴을 사용합니다. 이를 통해 코드의 가독성과 유지보수성을 향상시키고, 상태 변경에 따른 부작용을 줄이며 안정성을 유지할 수 있습니다.

Context를 사용하기 전에 고려해야 할 점

Context는 편리한 동시에 데이터 흐름을 이해하기 어렵게 만들 수 있는 단점이 있습니다. 데이터를 내려주기 위해 Properties를 사용하는 것은 조금 복잡할 수 있지만, 그로 인해 데이터 흐름이 명확하게 드러나는 장점이 있습니다.

만약 state를 공유하는 것이 복잡하거나 prop-drilling 때문에 개발이 어려운 상황이라면, 다음과 같은 대안을 먼저 고려해볼 수 있습니다.

  • 컴포넌트 합성

    복잡한 상태를 관리하기 위해 컴포넌트를 조합하고 중첩함으로써, 각 컴포넌트는 자체적인 상태를 가지고 관리할 수 있도록 합니다. 이렇게 하면 상태 관리가 더욱 명확해질 수 있습니다.

  • 상태 지역화

    필요한 상태를 가능한 한 해당 컴포넌트 내부로 지역화하여 관리합니다. 이렇게 하면 컴포넌트 간의 상태 공유가 줄어들어 데이터 흐름을 더 명확하게 만들어줍니다.

2. useReducer 로 상태 관리하기

Context는 상태뿐만 아니라 함수도 공유할 수 있는데, 이는 복잡한 상태 관리 로직과 라우터 이동(router.push)과 같은 함수들이 함께 작용할 수 있는 환경을 제공합니다. 하지만 상태와 다른 행동을 하는 로직이 섞이면 코드가 복잡해질 수 있습니다. 이런 경우에는 '관심사 분리'가 필요합니다.

useReducer는 상태 관련 로직을 이전 상태와 페이로드(payload)를 받아 다음 상태를 반환하는 순수 함수입니다. 이로써 컴포넌트 외부에서도 작성할 수 있고, 하나의 액션에서 여러 상태를 조회하고 동시에 수정하는 것이 가능합니다. 이로 인해 상태 관리의 관심사를 분리하고 코드를 단순화할 수 있습니다.

이는 모든 면에서 유리한 것만이 아닙니다. 실제로 작성하는 코드의 양은 useState보다 늘어날 수 있어, 간단한 상태에는 오히려 복잡도가 증가할 수 있습니다. 따라서, 상태가 커지고 복잡해지며 관심사 분리가 필요한 상황에서만 이를 적용하는 것을 권장합니다.

reducers 객체의 각 키 값은 dispatch의 타입(type)으로 사용되며, 해당 키에 해당하는 reducer 함수의 두 번째 매개변수는 payload가 됩니다. 일반적으로 reducer를 사용하려면 원하는 값을 수정한 후 새로운 state를 반환해야 하는데, 이 때 객체 복사와 같은 복잡한 작업이 필요했습니다. 그러나 createSlice 내부에서 Immer 라이브러리를 사용하여 간단하게 할당만으로도 state를 수정할 수 있습니다. 이를 통해 코드를 더 간결하게 작성할 수 있습니다.

따라서 createSlice는 RTK와 비슷한 스타일로 리듀서를 생성하고 관리할 수 있도록 도와주는 함수입니다. 이는 기존 Redux 개발 경험을 보다 편리하게 만들어주며, Immer 라이브러리의 도움으로 불변성 유지를 더욱 간단하게 처리할 수 있게 해줍니다.

import { useReducer } from 'react';

import { createSlice } from '@/utils/react/create-slice';

type GlobalStateType = {
  value: number;
};

const initialState: GlobalStateType = {
  value: 0,
};

const { reducer } = createSlice({
  initialState,
  reducers: {
    RESET: () => initialState,
    INCREASE_VALUE: (state, payload: number) => {
      state.value = state.value + payload;
    },
    DECREASE_VALUE: (state, payload: number) => {
      state.value = state.value - payload;
    },
  },
});

export const useExampleState = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return { state, dispatch };
};

Last updated