> For the complete documentation index, see [llms.txt](https://toktokhan.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://toktokhan.gitbook.io/docs/basic-guides/convention/contexts/code-style.md).

# Code Style

## 1. Context 사용 방법

{% tabs %}
{% tab title="Context 구현하기" %}
[`use-context-selector`](https://github.com/dai-shi/use-context-selector) 를 사용하여 구현합니다.

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

```tsx
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);
```

{% hint style="info" %}
**왜 전역 상태 라이브러리 대신 Context를 사용하나요?**<br>

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

{% tab title="Provider 로 Context 전달하기" %}
`SomeComponent`에서 `Count` 컨텍스트를 사용하려면, 해당 컨텍스트를 활용할 수 있도록 부모 요소에 Provider를 통해 컨텍스트를 전달해야 합니다. 이를 통해 `SomeComponent` 내부에서 해당 컨텍스트를 활용할 수 있는 상태가 되는 것입니다.

```tsx
const ParentComponent = () => {
  return (
    <CountProvider>
      <SomeComponent />
    </CountProvider>
  );
}; 
```

{% endtab %}

{% tab title="hoc 사용 고려하기" %}
컨텍스트를 사용하는 컴포넌트와 Provider를 한곳에서 관리하고 싶다면 `HOC` 를 고려해보세요

```tsx
const SomeComponent = () => {
  const count = useCountContext((ctx) => ctx.count);
  const setCount = useCountContext((ctx) => ctx.setCount);

  return (
    <Box>
      <Button onClick={() => setCount((cur) => cur + 1)}>{count}</Button>
    </Box>
  );
};

export default withCountProvider(SomeComponent)
```

```tsx
function withCountProvider<T extends (props?: any) => JSX.Element | null>(
  Component: T,
) {
  return function Wrapped(props: PropsOf<T>) {
    return (
      <CountProvider>
        <Component {...props} />
      </CountProvider>
    );
  };
}

export default withCountProvider;
```

{% endtab %}

{% tab title="context 사용하기" %}
`selector` 함수를 넘겨주어 컨텍스트를 사용합니다.

```typescript
const SomeComponent = () => {
  const count = useCountContext((ctx) => ctx.count);
  const setCount = useCountContext((ctx) => ctx.setCount);

  return (
    <Box>
      <Button onClick={() => setCount((cur) => cur + 1)}>{count}</Button>
    </Box>
  );
};
```

{% hint style="info" %}
**왜 Context 를 그대로 사용하지 않고 use-context-selector 라이브러리와 함께 사용하나요?**\
\
`context` 또한 제한적인 측면이 존재하기 때문입니다. 특히 `context`를 사용하는 컴포넌트는 해당 값이 변경될 때마다 불필요하게 다시 렌더링(re-rendering)되는 문제가 발생할 수 있습니다. 이는 원시 값이라면 괜찮을 수 있지만, 객체 안에 있는 특정 값만 필요한 경우에는 불필요한 렌더링을 초래할 수 있습니다.

이런 문제를 해결하기 위해 `use-context-selector` 라이브러리가 등장했습니다. 이 라이브러리는 **구독 패턴을 활용하여, `selector`가 반환하는 값이 변경될 때에만 컴포넌트를 다시 렌더링**합니다. 이로써 렌더링 효율을 향상시킬 수 있습니다. 사용할 때에 중요한 것은, 위의 예시처럼 <mark style="color:green;">**필요한 값을 가져오는**</mark><mark style="color:green;">**&#x20;**</mark><mark style="color:green;">**`selector`**</mark><mark style="color:green;">**를 라이브러리에 전달해주는 것입니다.**</mark>

{% endhint %}
{% endtab %}
{% endtabs %}

{% hint style="danger" %}
**Context를 사용하기 전에 고려해야 할 점**

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

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

* **컴포넌트 합성**

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

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

##

## 2. useReducer 로 상태 관리하기

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

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

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

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

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

```tsx
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 };
};

```

{% endtab %}

{% tab title="dispatch 로 상태 변경하기" %}

```tsx
const increase = (value: number) => {
    dispatch({
      type: 'INCREASE_VALUE',
      payload: value,
    });
  };

const decrease = (value: number) => {
    dispatch({
      type: 'DECREASE_VALUE',
      payload: value,
    });
  };

```

위와 같이 사용이 가능하며, reducer 이기 때문에 최후에 연산된 state 로 적용이 가능합니다.<br>

```tsx
const [value,setValue] = useState(0)
setValue(value + 2);
setValue(value + 3);

// bathcing 으로 인해 value: 3

increase(2);
increase(3);
// 연산을 기억하기 때문에 value: 5
```

{% content-ref url="/pages/qQGovyqpV9qJCcbhKNOZ" %}
[Batching 과 Snapshot 이란?](/docs/onborading/growing-up/react/batching-snapshot.md)
{% endcontent-ref %}
{% endtab %}
{% endtabs %}
