2024-07-14
좋은 코드를 찾아서 - createSafeContext
useContext를 어떻게 구성할까?
overlay-kit 라이브러리에서 context를 구성할 때 사용한 코드가 마음에 들어 글로 남기고자 한다.
매번 Provider 패턴의 useContext 코드를 구성할 때 어떻게 폴더 및 파일을 구성해야 할 지 고민이 되었는데,
이번에 살펴볼 createSafeContext 함수로 구성하면 깔끔하게 관리할 수 있게 된다.
import { type Provider, createContext, useContext } from 'react'
type NullSymbolType = typeof NullSymbolconst 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 // trueSymbol('null') === Symbol('null') // false디테일을 고려한 코드라는 생각이 들었다.
createSafeContext 함수 내부
createSafeContext 함수 내부에서는 Context를 생성하고 useSafeContext 커스텀훅을 구현한다.
이를 통해 응집도가 높아진 것 같다.
그리고 displayName도 고려한 부분이 인상적이다.
createSafeContext 함수의 반환 값은 [Provider, useContext]로 구성된다.
export type CreateContextReturn<T> = [Provider<T>, () => T]
// [Provider, useContext]여기서 제네릭을 사용해서 Provider에 대한 타입을 사용할 때 정의하도록 했다.
createSafeContext 함수 사용하기
export const [ContextProvider, useContext] = createSafeContext<ContextData>('tempContext')
export function useTemp() { return useContext().temp}
// ...Context를 반환하는 커스텀훅createSafeContext을 통해 사용하는 용도에 맞게 타입과 네이밍만 할당한 뒤 Provider와 useContext를 활용하면 된다.