전역 상태가 아닌 특정 컴포넌트의 상태를 관리할 때는 context API를 많이 활용한다.

이를 좀 더 편하게 사용하기 위해 아래와 같은 형태로 context 를 만들어서 사용한다.

 

// context/management

import React, { useContext, useEffect } from "react"
import { SOME_TYPE } from "./type/management";

interface IManagementState {}
interface IManagementAction {
  type:string;
  payload?:any;
}

const initState:IManagementState = {};

export const managementReducer = (state:IManagementState, action:IManagementAction) => {
  switch(action.type) {
    case SOME_TYPE : {
      // do something
      // ..
      return state;
    }
    default:
      return state;
  }
}

const managementContext = () => {

  const ctx = React.createContext<{
    state:IManagementState
    dispatch:React.Dispatch<IManagementAction>
  }>({
    state:initState,
    dispatch:() => null
  })

  const useCtx = () => {
    const c = useContext(ctx);
    return c;
  }

  return [
    ctx.Provider,
    useCtx,
    initState
  ] as const;
}

export default managementContext;


// context/type/management

export const SOME_TYPE = 'SOME_TYPE';


// page

import managementContext, { managementReducer } from '@/context/state'
import { useReducer } from 'react';

export const [ManagementContextProvider, useManagementContext, initManagementContextState] = managementContext();

export default function Page() {

  const [state, dispatch] = useReducer(managementReducer, initManagementContextState);

  return (
    <ManagementContextProvider 
    value={{
      state, 
      dispatch
    }}>
      {/* ... */}
    </ManagementContextProvider>
  )
}

 

Context Provider 를 state, dispatch 2개 각각 만들어서 성능최적화를 시켜줄 수 있지만.. 패스

 

context 의 상태는 reducer 를,

useCtx 함수를 통해 useContext 객체를 외부로,

useReducer 를 통해 context reducer 와 context initState 를,

이를 Provider를 통해 Context API 를 사용할 수 있도록 하는 구조이다  

 

하위 컴포넌트에서는 아래와 같이 export 한 useContext 객체를 손쉽게 사용할 수 있다

 

const { state, dispatch } = useManagementContext()

 

최근에 서로 다른 페이지에서 동일한 형태의 뷰를 갖는 컴포넌트를 추가해야 했는데,

기존의 컴포넌트는 위의 구조로 상태관리를 하고 있었다

 

기존에 사용하던 view 단의 context 가 위와 같이 managementContext 였다면

사실 managementContext2 로 복사해서 사용하면 그만이지만..

또 언제 같거나 비슷한 형태의 뷰를 추가해야할지 모르기에

HOC 를 이용하면 아래와 같이 간단한 수정으로 컨텍스트의 중복 없이 같은 구조의 Context 를 사용할 수 있다

 

// hoc/management.tsx

import managementContext, { managementReducer } from "@/context/state";
import { PropsWithChildren, useReducer } from "react";

export const [ManagementContextProvider, useManagementContext, initManagementContextState] = managementContext();

const ManagementContextHOC = ({ children }:PropsWithChildren<{}>) => {

  const [state, dispatch] = useReducer(managementReducer, initManagementContextState);

  return (
    <ManagementContextProvider
    value={{state, dispatch}}>
      {children}
    </ManagementContextProvider>
  )
}

export default ManagementContextHOC;

 

// page1.tsx
import ManagementContextHOC from '@/hoc/management';

export default function Page1() {

  return (
    <ManagementContextHOC>
      <SomeComponent />
    </ManagementContextHOC>
  )
}

 

// page2.tsx
import ManagementContextHOC from '@/hoc/management';

export default function Page2() {

  return (
    <ManagementContextHOC>
      <SomeComponent />
    </ManagementContextHOC>
  )
}

 

context api + useReducer + HOC 를 이용한 위와 같은 구조는

무분별한 전역 상태 관리가 아닌

필요한 뷰 단에서의 상태관리를 구분짓기에 좋은 구조라고 생각한다

'web > React' 카테고리의 다른 글

digital envelope routines::unsupported  (0) 2023.06.20
React-Query Dynamic Parallel Queries  (0) 2023.06.06
제너레이터(Generator)  (1) 2021.01.11
Redux 비동기(redux-saga)  (0) 2021.01.11
리액트 내장 훅  (0) 2021.01.01

+ Recent posts