<aside> <img src="/icons/bookmark_purple.svg" alt="/icons/bookmark_purple.svg" width="40px" />

목차

</aside>

소켓 이벤트 리스너의 등록 타이밍

현재 room이라는 하나의 큰 컴포넌트내에서 questionView와 resultView로 뷰가 나뉘어져있다.

questionView: 질문 응답 뷰

resultView : 통계결과를 표시하는 뷰

기존 구현 방식

questionView(질문응답 뷰) → resultView(통계결과 뷰)로 넘어올 때,

questionview에서 마지막 질문이 끝나면 뷰를 전환을 시키고, resultview가 렌더링 되면서, 소켓 이벤트를 등록했다.

하지만 소켓 이벤트를 등록했음에도 결과뷰 렌더링시 서버로부터 응답이 오지 않았는데,

그 이유는 뷰가 전환되는 사이에 통계 알림이 오고, 그 이후에 이벤트 리스너가 늦게 등록되었기 때문이다.

마지막 질문이 끝나면서 타이머가 종료되고, 페이지 전환하고(startResultLoading), 로딩을 시작(startResultLoading)했을때는, 이미 통계결과가 와있어서 그 이벤트 헨들러인 finishloading이 먼저 진행되고있었다.

예상 순서 : startReesultPage → startResultLoading → finishResultLoading

테스트 결과: finishResultLoading → startReesultPage → startResultLoading

image (3).png

if (socket && socket.connected) {
        const handleResult = (response: CommonResult) => {
          if (Object.keys(response).length > 0) {
            finishResultLoading();
            ...
          } else {
            openToast({ type: 'error', text: '통계 분석 중 오류가 발생했습니다. 다시 시도해주세요' });
          }
        };
        socket.on('empathy:result', handleResult);
      }

수정된 방식

기존 resultview에서 소켓 리스너를 등록하는것이 아닌 뷰 전환이 되기 전, questionview 에서 미리 이벤트리스너를 등록해주면된다.

  1. 즉, 타이머가 종료될 때까지 전역 상태로 관리되는 통계처리를 지연시키고, 소켓 응답의 response를 임시 데이터로 저장한다.

    const handleResult = (response: CommonResult) => {
        // 통계결과를 임시로 저장
        resultResponseRef.current = response;
      };
    ...
    socket.on('empathy:result', handleResult);
    
  2. 마지막 질문이 끝났을 때 로딩을 띄우고, 그때서야 임시로 저장돼있던 통계데이터를 처리한다.

    useEffect(() => {
        if (currentQuestionIndex >= questions.length) {
          timerWorker.current?.postMessage({ action: 'stop' }); // 타이머 중지
    
          if (questions.length > 0) {
            //마지막 질문이 끝나고 로딩 시작
            onLastQuestionComplete();
            startResultLoading();
    
            setTimeout(() => {
              if (resultResponseRef.current) {
                // 통계 데이터 처리
                setStatisticsKeywords(resultResponseRef.current);
                Object.entries(resultResponseRef.current).forEach(([userId, array]) => {
                  setParticipants((prev) => ({ ...prev, [userId]: { ...prev[userId], keywords: array } }));
                });
              } else {
                openToast({ type: 'error', text: '통계 분석 중 오류가 발생했습니다. 다시 시도해주세요' });
              }
    
              finishResultLoading();
              setOutOfBounds(false); //사용자 ui 원위치로
              socket?.off('empathy:result', handleResult);
            }, 3000);
          }
    
          return;
        }
    

    이렇게 하면, 뷰 전환 이전에 소켓 응답이 오더라도 놓치지 않고 결과를 처리할 수 있게 된다.

왜 setTimeout으로 지연을 줘야할까?

뷰 전환이나 타이밍 제어가 어려운 경우, 일부러 로딩 시간을 주입하여 서버로부터의 응답을 기다린 후에 결과를 처리하는 것이 더 자연스러울 수 있다.

여기서는 의도적으로 3초 동안 로딩을 표시한 뒤에 통계 결과를 보여주거나 오류 메시지를 띄우는 방식으로 처리했다.

로딩을 통한 지연을 일부러 주입시킨 이유는 다음과 같다.

웹워커의 도입이유와 새로운 문제