개발자
류준열
훅, 컴포넌트 밖에서 사용가능한 toast 유틸 만들기
React에서 useToast()를 커스텀 훅으로 만들어쓰고 있었다.
const toast = useToast();
toast({
type: "success",
content: "저장되었습니다!",
});
useToast 훅의 문제점
혹은 컴포넌트 내부에서만 호출 할 수 있다는 제약때문에 React 외부에서는 토스트를 띄울 수 없다.
axios intercepter에서 토큰만료 안내를 할 때는 브라우저 alert를 사용해야했다.
axios.interceptors.response.use(
(res) => res,
async (error: AuthAxiosError) => {
...
const isTokenExpired =
status === 401
if (isTokenExpired && error.config.url === REFRESH_API_URL) {
...
alert("세션이 만료되어 로그인 페이지로 이동합니다.");
logout();
...
}
},
);
우리팀의 제품은 일관된 UI를 지향하는데, React의 제약에 체념한 상태였다.
하지만 'this는 메소드가 자신을 호출한 객체를 가리킨다' 는 성질을 이용하여 React 외부에서도 토스트를 띄울 수 있다.
전역 toast 유틸 객체 만들기
utils폴더안에 toast.ts를 다음과 같이 만들자.
// toast.ts
const toast = {
showToast: (newToastOption: ToastOptionType) => {
console.warn("ToastProvider가 아직 초기화되지 않았습니다.");
},
_setFunctions(add: (newToastOption: ToastOptionType) => void) {
this.showToast = add;
},
};
export default toast;
이 toast 객체는 React 바깥을 포함한 모든 전역에서 접근 가능하다. 초기에는 아무 기능도 없지만, 아래와 같이 Provider가 마운트되면서 내부 함수를 주입한다.
Provider안에서 함수 주입과 this 바인딩
export const Provider= ({...}:ToastProps) => {
...
const showToast = useToast();
useEffect(() => {
toast._setFunctions(showToast);
}, []);
...
return <>{children}</>
}
toast._setFunction는 this가 해당 객체(toast)를 가리킨다. 덕분에 toast.showToast에 내부 함수를 주입 할 수 있다.
사용방식
// utils/handleError.ts
import toast from "@/utils/toast";
export function handleError(message: string) {
toast.showToast({
type: "error",
content: `에러 발생: ${message}`,
});
}
정리
React 커스텀 훅은 범위에 제한이 있다. 그 한계를 벗아나기 위해서는 this 바인딩을 활용하는 것이 좋은 해결책이 될 수 있다.
이 패턴을 구현하면서 클로저를 다시 한번 느꼈다. useToast가 선언될때의 setState는 렉시컬환경에 기록되어 있기 때문에 해당 함수가 react 외부의 toast 객체로 넘겨져도 setState를 실행할 수 있는 것이다.
이런 성질을 이용해서 toast뿐 아니라, 전역 알림, 로딩 처리등 모든 컴포넌트에 확장할 수 있다.
zustand getStore 쓰지 않은 이유
당시에는 근본적인 해결책은 react외부에서 setState를 일으켜야 하는 것이고 그러다보니까 zustand는 오히려 react에 해당하는 내 방향성과 대척점에 있는 영역이라고 생각했다.
나중에 zustand getState로 쉽게 해결할수있다는걸 알게되었다.
의의를 생각해보자면 context, zustand 등 라이브러리와 무관하게 react 외부에서 일으킬 수 있는 방식을 썡으로 구현하면서 react가 프레임워크가 라이브러리라는 것을 몸소 느낄 수 있는 경험이었다.