jgjgill

좋은 코드를 찾아서 - createSafeContext

No Filled

useContext를 어떻게 구성할까?

overlay-kit 라이브러리에서 context를 구성할 때 사용한 코드가 마음에 들어 글로 남기고자 한다.


매번 Provider 패턴의 useContext 코드를 구성할 때 어떻게 폴더 및 파일을 구성해야 할 지 고민이 되었는데, 이번에 살펴볼 createSafeContext 함수로 구성하면 깔끔하게 관리할 수 있게 된다.


create-safe-context.ts

import { type Provider, createContext, useContext } from 'react'

type NullSymbolType = typeof NullSymbol
const NullSymbol = Symbol('Null')

export type CreateContextReturn<T> = [Provider<T>, () => T]

export function createSafeContext<T>(displayName?: string): CreateContextReturn<T> {
  const Context = createContext<T | NullSymbolType>(NullSymbol)
  Context.displayName = displayName ?? 'SafeContext'

  function useSafeContext() {
    const context = useContext(Context)

    if (context === NullSymbol) {
      const error = new Error(`[${Context.displayName}]: Provider not found.`)
      error.name = '[Error] Context'

      throw error
    }

    return context
  }

  return [Context.Provider as Provider<T>, useSafeContext]
}

Symbol의 활용

여태까지 useContext의 초깃값 null을 많이 사용해왔다.

그런데 라이브러리에서는 Symbol('Null')을 사용했다.


Symbol원시 값을 반환하는 내장 객체로 매번 고유한 심볼을 반환한다.

null === null // true
Symbol('null') === Symbol('null') // false

디테일을 고려한 코드라는 생각이 들었다.


createSafeContext 함수 내부

createSafeContext 함수 내부에서는 Context를 생성하고 useSafeContext 커스텀훅을 구현한다.

이를 통해 응집도가 높아진 것 같다.

그리고 displayName도 고려한 부분이 인상적이다.


createSafeContext 함수의 반환 값은 [Provider, useContext]로 구성된다.

export type CreateContextReturn<T> = [Provider<T>, () => T]

// [Provider, useContext]

여기서 제네릭을 사용해서 Provider에 대한 타입을 사용할 때 정의하도록 했다.


createSafeContext 함수 사용하기

context.ts

export const [ContextProvider, useContext] = createSafeContext<ContextData>('tempContext')

export function useTemp() {
  return useContext().temp
}

// ...Context를 반환하는 커스텀훅

createSafeContext을 통해 사용하는 용도에 맞게 타입과 네이밍만 할당한 뒤 ProvideruseContext를 활용하면 된다.

@2023 powered by jgjgill