프로그래머스 프론트엔드 데브코스 5기에서 마지막으로 진행한 프로젝트!
마지막 프로젝트답게 8주라는 기간과 BE분들과의 협업을 통해 이루어졌다.
당장 엊그제인 2024.03.22(금)이 제출마감기한이었고 이미 제출은 끝났다.
하지만 아쉬운점이 남아있기도 하고 다른 팀원분들도 리팩토링 의사가 있으셔서 나 또한 맡은 부분을 꼭 리팩토링 하고싶었다.
이번 주말을 활용해 리팩토링을 진행해 보았다.
처음 만들었던 useSSE
라는 훅은 이름 답게 Server-sent-event를 활용하는 훅이다.
다만 굉장히 한정적인 상황에서만 쓸 수 있었다.
const useSSE = ({ url, options = {} }: UseSSEProps) => {
const [error, setError] = useState<SSEErrorType>(null);
const [data, setData] = useState<I_ChangeGameRoomData[]>([]);
const isError = useMemo(() => !!error, [error]);
const memorizedOptions = useRef(options);
useEffect(() => {
const sse = new EventSourcePolyfill(url, { ...memorizedOptions.current });
const handleChangeGameRoom = (e: Event) => {
const messageEvent = e as MessageEvent;
const data = JSON.parse(messageEvent.data);
setData(data);
};
const handleInitConnect = (e: Event) => {
const messageEvent = e as MessageEvent;
const data = JSON.parse(messageEvent.data);
setData(data);
};
sse.onopen = () => {
setError(null);
sse.addEventListener(SSE_CHANGE_GAME_ROOM, handleChangeGameRoom);
sse.addEventListener(SSE_CONNECT, handleInitConnect);
};
sse.onerror = (e: Event) => {
setError(e);
};
return () => {
sse.removeEventListener(SSE_CHANGE_GAME_ROOM, handleChangeGameRoom);
sse.removeEventListener(SSE_CONNECT, handleInitConnect);
sse.close();
};
}, [url, memorizedOptions]);
return {
data,
isError,
error,
};
};
위 코드의 문제점은 무엇일까?
크게 생각나는건 다음과 같다.
myEvent
라는 이벤트 이름으로 데이터를 받고싶을땐, 새로 만들어야 한다.문제점을 인지하였다. 그러면, 외부에서 이벤트이름을 주입받게 하고 데이터의 타입도 주입받게 하면 될 것 같다.
interface UseSseProps<K> {
url: string;
customEvents?: K[];
options?: EventSourcePolyfillInit;
}
const useSse = <K extends string, T = unknown>({
url,
customEvents,
options = {},
}: UseSseProps<K>) => {
const [isConnected, setIsConnected] = useState(false);
const [data, setData] = useState<T>();
const [error, setError] = useState<SseErrorType>(null);
const isError = useMemo(() => !!error, [error]);
const memorizedOptions = useRef(options);
useEffect(() => {
const eventSource = new EventSourcePolyfill(url, {
...memorizedOptions.current,
});
const handleCustomEventData = (e: Event) => {
const messageEvent = e as MessageEvent;
const data = JSON.parse(messageEvent.data);
setData(data);
};
eventSource.onopen = () => {
setError(null);
setIsConnected(true);
customEvents?.forEach((customEvent) =>
eventSource.addEventListener(customEvent, handleCustomEventData)
);
};
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
setData(data);
};
eventSource.onerror = (e) => {
setError(e);
setIsConnected(false);
};
return () => {
customEvents?.forEach((customEvents) =>
eventSource.removeEventListener(customEvents, handleCustomEventData)
);
eventSource.close();
};
}, [url]);
return { isConnected, data, error, isError };
};
EventSourcePolifyll
에 커스텀 이벤트 핸들러를 부착할땐 파라미터가 무조건 Event
를 수신하게 되어있다. 따라서 as
키워드로 강제 타입변환을 진행(라이브러리임)이전과 크게 바뀌진 않았다. 다만 새로운 이벤트 네이밍과 새로운 데이터를 받을 수 있게 변화했다.
잘 해결 된 것 같지만, 아직도 문제점이 남아있는 것 같다...!
이벤트 핸들러는 데이터만 저장한다. 특정 이벤트를 수신했을때 원하는 함수를 호출할 수 없다.
물론, 외부에서 이렇게 사용할수 도 있다.
const { data } = useSse<...>(...);
useEffect(() => {
if(data.name === 'my'){
...
}
//data는 보통 객체지만...편의상 이렇게 진행함.
}, [data])
이게 과연 맞는걸까?
하지만 개선한다고 외부에서 이벤트 핸들러를 부착하게 된다면, 인자로 받아오는 값이 꽤 많아질 것이다.
const { data } = useSse<...>({
//아마 아래와 같이 받아야하지 않을까...?
customEvents:[
{ eventName:'myEvent', handler: (e) => {...} },
{ ... }
]
});
아직 무엇이 맞는지는 모른다...🙃
개선점으로 적어두고 나중에 실력이 향상됐을때 보면, 또 다른 관점을 얻을 수 있지 않을까?