본문 바로가기
카테고리 없음

[React] 중복된 Event Listeners 관리하기(feat. socket io)

by HarryJang 2023. 7. 31.

목차

    개요

    react 로 web frontend 를 개발하다 보면 어떠한 event 들을 감지하고 그에 따른 반응을 해줘야할 때가 있다. 예를 들어, 내가 현재 개발하고 있는 게임 웹 사이트에서는 게임을 추천하는 기능이 있다. 그리고 게임을 추천하면 유저에게 "게임을 추천했습니다", 혹은 "이미 추천한 게임입니다" 등의 알람을 띄워준다. 그렇다면 유저가 추천 버튼을 누르는 이벤트를 감지해서 알람을 띄워주는 반응을 하도록 개발을 하면 된다.

     

    문제

    간단해보이는 작업 같지만 나 같은 초보에게는 문젯거리가 될 수 있다. 실제로 이 기능을 구현하는 과정에서 이에 관해 겪은 문제가 있다. 바로 추천 했을 때 알람을 한 번만 띄우는 것이 아니라 여러번 띄운다는 것이다.

     

    좀 더 디테일하게 메커니즘을 설명해보겠다.

     

    event listener에는 여러가지 종류가 있(는 것 같)다. 그 중에서 나는 socket.io 를 사용해서 backend server 와 frontend client 가 communication 할 수 있도록 하는 socket 을 연결시켜놨다. 그래서 유저가 추천 버튼을 누르면 frontend 에서 backend에 신호를 보내어 추천하는 게임 리스트를 업데이트하고 그 리스트를 frontend 로 다시 보내 화면에 보여주는 방식이다. 그 과정에서 리스트 업데이트 시에 알람을 띄워야할 케이스에는 알람을 띄우는 방식으로 코드를 짰었다.

     

    그런데 한 번만 떠야할 알람이 여러 번 뜨는 것이다. 분명 server 에서는 client 에게 한 번만 emit 했는데 여러 개의 알람이 뜬 것이다. 쉽게 말해 나는 택배를 한 번 보냈는데 여러 명이 받았다고 하는 꼴이다.

     

    문제는 여러명의 event listener 가 생성됐기 때문에 일어났다.

     

    해당 웹페이지가 rendering 될 때 client 쪽에 여러 개의 socket.on 이 생성되었고 server 에서 해당 socket 에 신호를 보낼 때 마다 여러 개의 socket 이 반응했던 것이다.

     

    당시의 코드를 살펴보자.

    socket.on('gameroom_updateRecommendation', ({toPlayNext}) => {
    	setRecommendedGame(convert2ToPlayNextButton(toPlayNext))
    })
    socket.on('gameroom_recommendationAlarm', ({message, showMessage}) => {
    	if (showMessage) toast.success(message)
    })

    위의 코드가 socket event listener 를 생성하고, 이는 rendering 될 때 마다 생성되는 것이다. 웹 페이지가 rendering 될 때 마다 알람이 하나씩 추가된다는 말이다.

    해결

    이런 저런 시행 착오 끝에 문제를 해결할 수 있었다. 시행 착오 가운데 배웠던 것들을 한 번 살펴보겠다.

     

    socket.once

    우선 처음으로 사용했던 방법은 socket.once 이다.

     

    socket.once 는 socket.on 과 달리 생성되고 실행된 뒤 스스로를 삭제한다. 이를 사용하면 rendering 이 될 때 마다 새로운 listener 가 생겨나고 작동된 뒤 사라진다. 이를 통해 항상 1개의 listener 를 유지하는 게 가능하다.

     

    그러나 문제가 완전히 해결되지 않았다. 모종의 이유로 페이지가 가장 처음 rendering 될 때 여러 개의 listener 가 생겨났고 이는 어떠한 listener 도 작동되기 전에 생겨난거라 그대로 여러 개가 쌓여있게 된다. 그래서 한 번 추천을 한 뒤로는 괜찮지만 가장 처음 게임을 추천할 때는 여러 개의 alarm 이 뜨는 에러가 남아있었다. 이를 해결하기 위해서는 다른 방법이 필요했다.

     

    useEffect ( feat. removeAllListeners)

    그리고 찾게된 해답이 바로 useEffect 이다. useEffect 의 depecdency list 에 아무것도 넣지 않으면 useEffect 는 처음 rendering 될 때만 실행된다. 따라서 socket 생성을 useEffect 안에 넣어 처음 한 번만 생성되게 하면 된다. 참으로 간단하게 해결된다.

     

    추가적으로 event listener 를 생성하면 언젠가는 필요가 없을 때 그것을 삭제해줘야 한다. 삭제되지 않고 남아있는 listener 들을 메모리를 차지하고 컴퓨팅 능력을 저하시킬 수 있기 때문이다. event listener 를 useEffect 안에서 생성한 경우에는 return value 에 listener 를 삭제하는 코드를 넣어준다면 unmount 될 때 (더 이상 listener 가 필요하지 않을 때) 알아서 listener 들을 삭제해준다.

    useEffect(() => {
    	socket.on('gameroom_updateRecommendation', ({toPlayNext}) => {
    		setRecommendedGame(convert2ToPlayNextButton(toPlayNext))
    	})
    socket.on('gameroom_recommendationAlarm', ({message, showMessage}) => {
    	if (showMessage) toast.success(message)
    })
    	return () => {
    		socket.removeAllListeners('gameroom_updateRecommendation')
    		socket.removeAllListeners('gameroom_recommendationAlarm')
    	}
    	// eslint-disable-next-line
    }, [])

    위 코드에서 보이다 시피 removeAllListeners 를 통해 생성된 listener 들을 다 삭제 하도록 하고 그것을 callback function 으로 만들어 returun 해주면 된다.

     

    참고 글

     

    Using the Effect Hook – React

    A JavaScript library for building user interfaces

    legacy.reactjs.org

    마무리

    아직 React 공부를 시작한지 두 달된 초보라 기본적인 것도 배울게 많은 것 같다. 지금까지는 크게 생각지 못하면서 사용했던 event listener 들이 생성되고 삭제되는 것을 배우고 이를 잘 관리하는 것이 기능성이나 효율성 측면에서 중요하다는 것을 알 수 있었다. 앞으로 이처럼 기본을 잘 다지면서 배워가면 좋겠다!