Context API 초기값 설정과 ts

bloom74·2024년 1월 3일

Context API 초기값 null 설정시 타입 오류

프로젝트를 타입스크립트로 마이그레이션 하면서 context API를 초기화하는 방법에 여러가지 방법이 있다는 사실을 알게 되었다.

interface CategoryItem {
  _id: string;
  name: string;
}

interface CategoryContextType {
  categoryList: CategoryItem[] | null;
  setInitialCategoryList: (data: CategoryItem[]) => void;
}

interface CategoryContextProviderProps {
  children: ReactNode;
}

//null로 초기화 
const CategoryContext = createContext<CategoryContextType | null>(null);

export function CategoryContextProvider({
  children,
}: CategoryContextProviderProps) {
  const [categoryList, setCategoryList] = useState<CategoryItem[] | null>([]);

  const setInitialCategoryList = useCallback((data: CategoryItem[]) => {
    setCategoryList(data);
  }, []);
  const value = useMemo(
    () => ({
      categoryList,
      setInitialCategoryList,
    }),
    [categoryList, setInitialCategoryList]
  );
  return (
    <CategoryContext.Provider value={value}>
      {children}
    </CategoryContext.Provider>
  );
}

export default CategoryContext;

위와 같이 CategoryContext를 만들었다. 하지만 CategoryContext를 사용하는 곳에서 에러가 났다.

타입스크립트에서 null로 초기화된 컨텍스트는 컨텍스트 값이 null이거나 실제 제공된 타입일 수 있습니다. 이 경우, 컨텍스트를 사용하기 전에 해당 값이 null이 아닌지 검사하는 것이 필수적이다.

이 문제를 해결하는 데에는 여러가지 방법이 있는데, 크게 Context API 초기설정을 수정하는 방식과 사용하는 쪽에서 타입 narrowing을 하는 방법이 있다.

해결법

null check을 통한 해결법

먼저 null check를 하는 방법은 아래와 같다. context를 사용하는 쪽에서 context가 null인지 type check를 해서 사용해야 한다.

  async function handleLogin() {
    try {
      //...
      //categoryContext의 유무에 따른 구조분해 할당
      if (categoryContext) {
        const { setInitialCategoryList } = categoryContext;
        setInitialCategoryList(response.data.categories);
      } else {
        new Error("CategoryContext 없음")
     //...
    } catch (err) {
     //...
  }

하지만 우리가 context를 null인 상태로 사용하는 경우는 흔치 않다. 그렇기 때문에 context 초기화 null이 아닌 값으로 하는 방법을 찾아보았다.

명시적인 context 초기값을 주는 방법

가장 먼저 context의 type이 정해져 있는 경우,

const CategoryContext = createContext<CategoryContextType>({
   categoryList: [],
  setInitialCategoryList: () => {},
});

위와 같이 필요한 키값들로 초기화 시켜주는 것이다. 이 방식은 컨텍스트가 항상 객체 형태를 가지며, null이 될 수 없다. 따라서 타입스크립트 type checking에서 null check가 필요하지 않다.

non-null assertion operator(!)를 활용하는 해결법

non-null assertion operator를 사용해서 타입 검사기에게 해당 값이 결코 null이나 undefined가 아니라고 알리는 것입니다.

  const categoryContext = useContext(CategoryContext);

  //...
 
  async function handleLogin() {
    try {
 	//...
    //non-null assertion operator ! 사용 
      categoryContext!.setInitialCategoryList(response.data.categories);
      navigate("/initial-resource-form", { state: { isInitialUser: true } });

      return;
    }
	//...
    } catch (err) {
     //...
    }
  }

타입 단언을 통한 해결법

마지막으로 타입 단언을 통한 해결법이 있다.타입 단언은 개발자가 해당 타입에 대해 확신이 있을 때 사용하는 타입 지정 방식이다. 타입 단언시, 타입스크립트는 컴파일 할 때 특별히 타입을 체크하지 않고, CategoryContextType의 모든 속성을 포함하지 않더라도 에러를 발생시키지 않는다.

const CategoryContext = createContext<CategoryContextType>(
  {} as CategoryContextType
); 

이 방법은 편리하지만, 개발자가 실수로 필요한 속성을 빼고 작성할 수 있기에, 권장되지 않는 방법이다.

결론

각자의 상황에 맞게 사용하면 좋겠지만, 개인적으로 타입을 명시적인 context 초기값을 주는것이 가장 오류 발생을 막는 방법이라고 생각해서 명시적인 context 초기값을 줌으로써 문제를 해결하였다.

참고자료

typescript context api
stackoverflow - React createContext(null) not allowed with Typescript?
Non-Null Assertion Operator

0개의 댓글