ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React Hook] 상태 훅(State Hook)을 EventListener에 사용시 유의사항
    Javascript & TypeScript 2021. 2. 11. 16:06
    반응형

    개인프로젝트에서 HTMLAudioElement 를 선언하여 해당 객체에 이벤트 리스너를 심어 음악재생을 관리하는 기능을 구현하던 중 겪은 현상을 정리하고자 이 글을 작성하였다.

     

    초기 소스는 아래와 같이 작성했다.

    function MusicPlayer() {
    
    const [totalTime, setTotalTime] = useState(1); // 초기값을 0으로 주면 바로 다음 음악재생
    const [currentTime, setCurrentTime] = useState(0);
    const [audio] = useState(new Audio());
    
     function init() {
        console.log(`init ${MusicPlayer.name}`);
        
        audio.addEventListener('timeupdate', function() {
          const currentTime = audio.currentTime;
          const totalTime = audio.duration;
           if(currentTime >= totalTime) {
             console.log('do next');
             setNext();
           }
          setCurrentTime(currentTime);
          setTotalTime(totalTime);
        });
        audio.addEventListener('play', function() {
          // H/W에서 제어했을때도 State의 변경값 확인이 필요하므로 eventlistener 필요
          setIsPlay(true);
        });
        audio.addEventListener('pause', function() {
          setIsPlay(false);
        });
      }
      
      function setNext() {
        let nextIdx = currentPlayIdx + 1;
        if(nextIdx > playIndexes.length){
          nextIdx = 0;
          if(!isRepeat){
            audio.pause();
          }
        }
        setCurrentPlayIdx(nextIdx);
      }
    }

    audio 객체의 timeupdate 이벤트 리스너를 통해 현재 재생중인 음악이 완료되면, 다음 음악을 재생하도록 만든 로직이다. 음악 재생이 완료되면 currentTime >= totalTime 조건이 true가 되기 때문에 "do next" 로그 이후 작업이 수행되며, currentPlayIdx 상태 훅(State Hook)값을 변경한다.

     

    소스상에는 표시되지 않았지만, currentPlayIdx가 변경될때 발생하는 Effect Hook에서 다음 음악을 재생하는 코드가 구현되어 있다.

     

    구현완료 후 테스트를 하니 0번째 음악이 잘 재생되었고, 1번째 음악으로도 잘 넘어갔다.

    문제는 그 다음 음악에서 발생했다. 음악이 재생되지 않았다.

     

    처음엔 다음 음악을 재생시키는 setNext() 함수에 문제가 있나 싶어 버튼에 setNext를 바인딩시킨 뒤 직접 호출했다.

    <IconButton aria-label="next" onClick={setNext}>
      {theme.direction === 'rtl' ? 
      <SkipPrevious className={classes.Icon} /> : 
      <SkipNext className={classes.Icon} />}
    </IconButton>

    호출결과는 성공적이었다. Index 값이 변경되면서 다음음악이 선택되었다.

    이벤트리스너에 있어서 문제가 되었던건 알겠는데 여전히 의문은 남았다. 왜 다른 상태 훅(State Hook)은 잘되는가?

    setCurrentTime(currentTime); // 정상동작
    setTotalTime(totalTime); // 정상동작
    setIsPlay(true); // 정상동작
    setIsPlay(false); // 정상동작

    처음엔 함수 호출을 통해 간접적으로는 동작 안하는 건줄 알았다. 그러다 정상 동작하는 다른 상태 훅(State Hook)값을 보니 깨닫게 되었다.

     

    "이벤트리스너안에선 상태 훅 값을 가져와 쓸 수 없다."

     

    정상적으로 동작한 상태 훅은 고정된 값이 있거나, 이벤트 리스너 내부의 값을 설정하는 경우에는 동작을 했지만, setNext함수와 같이 내부에 값을 가져와 다시 설정하는 경우에는 동작하지 않았다. 따라서 이 문제를 해결하려면 아래와 같이 useEffect 를 추가하는 방식으로 수정할 수 밖에 없었다.

      useEffect(() => {
        if(currentTime >= totalTime) {
          setNext();
        }
      }, [currentTime, totalTime]);

     

    한줄요약 : 이벤트리스너 안에선 상태값 못 쓴다. set만 된다.

    반응형

    댓글

Designed by Tistory.