Context(전역 상태 관리) 대신 Event bus를 사용하여 리렌더링을 개선할 수 있습니다!
Event bus 조사 동기
Context는 불필요한 리렌더링을 너무 많이 발생시키기 때문입니다. 컴포넌트와 컴포넌트 사이에서 정보를 보내는 방법은 유명한 것으로 두 개가 있습니다.
- 상-하위 컴포넌트 관계라면 prop으로 보내기
- reducer, recoil 등의 전역 상태 라이브러리를 사용하기
props나 전역 상태 라이브러리를 사용하는 것은 간편하나 상위 컴포넌트 하위의 컴포넌트들의 리렌더링이 발생한다는 단점이 있습니다.
React Global Context를 사용하는데 단 하나의 Context에서 모든 상태를 관리하니 불필요한 리페인트가 발생했습니다. 예를 들면 사진에서 I와 D가 소통을 해야하는데 굳이 A에서 상태를 prop으로 내려주어야 하는 상황입니다. A에서의 리렌더링이 발생하면 하위 컴포넌트들은 모두 리렌더링이 발생합니다.
필요한 기능들이 추가되면서 슬슬 성능을 신경쓰어야 한다고 생각이 들었습니다. 전역에서 내려주는 context 혹은 prop 형태의 컴포넌트 간 데이터 전달과 다른 방식을 알아 볼 필요가 있었습니다.
이벤트 버스라는 것을 공부해보다
유튜버와 구독자의 관계라고 이해하시면 편합니다.
https://dawchihliou.github.io/articles/event-bus-for-react
이벤트 버스는 컴포넌트 간에 Publish-Subscribe 형식의 통신을 허용하는 디자인 패턴입니다.
이벤트 발신 컴포넌트는 메시지가 어디로 전송되는지 몰라도 이벤트 버스로 메시지를 보낼 수 있습니다. 반면에 이벤트 수신 컴포넌트는 이벤트 버스에서 메시지를 수신하고 메시지가 어디에서 왔는지 몰라도 메시지를 통해 특정 작업을 진행할 수 있습니다.
이 방식을 사용하면 컴포넌트가 서로를 알지 못해도 통신할 수 있지요.
- 이벤트: 이벤트 버스에서 송수신되는 메시지입니다.
- 게시자: 이벤트를 발신하는 발신자입니다.
- 구독자: 이벤트를 수신하는 수신자입니다.
간단한 사용 방법
React에서 이벤트 버스 패턴을 사용하려면, 우선 Event Emitter를 생성해야 합니다.
Node.js의 events
모듈을 사용하면 간단하게 구현할 수 있습니다.
// 이벤트 버스 생성 컴포넌트
import { EventEmitter } from 'events';
const eventBus = new EventEmitter();
export default eventBus;
이제 이벤트를 발행하거나 수신하는 컴포넌트에서 이 eventBus
를 import 해서 사용하면 됩니다.
event 라는 모듈은 nodejs에 포함된 것이어서 다른 패키지를 다운로드 받을 필요가 없습니다!
// 이벤트를 발행하는 컴포넌트
import eventBus from './eventBus';
function PublisherComponent() {
const handleClick = () => {
eventBus.emit('myEvent', 'Hello, Event Bus!');
};
return <button onClick={handleClick}>Click Me!</button>;
}
export default PublisherComponent;
emit이라는 것을 사용해서 이벤트를 발행할 수 있습니다. 해당 함수의 첫 번째 매개변수는 이벤트의 key이며, 두 번째 매개변수는 이벤트 전달 값입니다. 이벤트 전달 값은 어떤 값이든지 넣을 수 있습니다.
// 이벤트를 수신하는 컴포넌트
import { useEffect } from 'react';
import eventBus from './eventBus';
function SubscriberComponent() {
useEffect(() => {
const handleMyEvent = (message) => {
// 보내준 값(메시지) 출력하기
console.log(`Received: ${message}`);
};
// 이벤트 수신하기
eventBus.on('myEvent', handleMyEvent);
return () => {
// 컴포넌트가 사라질 때에는 이벤트 수신 종료
eventBus.off('myEvent', handleMyEvent);
};
}, []);
return <div>Subscriber Component</div>;
}
export default SubscriberComponent;
이렇게 하면, PublisherComponent
에서 버튼을 클릭하면 myEvent
이벤트가 발행되고, SubscriberComponent
에서는 이 이벤트를 수신하여 콘솔에 메시지를 출력합니다. 생각보다 사용법이 그렇게 어렵지 않습니다!
회사 프로젝트에 적용하기
프로젝트 내에서 특정 탭을 열었다 닫는 행동을 두 번 반복했습니다.
원래는 이처럼 패널이 열리기만 해도, 특정 컴포넌트의 렌더링을 기준으로 했을 때 42번의 렌더링이 발생합니다.
개선 결과 리렌더링이 12번으로 줄어들었습니다. 컴포넌트 기준으로 약 72%의 렌더링 감량 효과가 있는 것입니다.
🔔 이 예시는 context를 변경하는 것은 Provider 하위의 컴포넌트에 리렌더링을 발생시키며, 이를 event Bus를 통해 조금이나마 해결할 수 있음을 암시합니다.
🤷♂️ 다만, event bus는 이벤트를 게시하고 구독할 수 있을 뿐 데이터를 저장하지는 않습니다. context를 완전히 대체하기 위해서는 event bus 이용 시 데이터 저장 방식을 고안해야 합니다.
<도움과 출처>
https://dawchihliou.github.io/articles/event-bus-for-react
https://nodejs.org/api/events.html#class-eventemitter
https://www.scaler.com/topics/react/prop-drilling-in-react/
'Develop' 카테고리의 다른 글
Jest 테스트 코드의 효과와 예시 연구 (0) | 2024.07.20 |
---|---|
lazy loading으로 Entry Point File 크기 줄이는 방법 연구 (0) | 2024.07.20 |
drag event 성능 개선을 위한 JS web worker 사전 조사 (0) | 2024.07.20 |
React : performamce, profiler를 통한 성능 개선 측정하기 (drag & drop, state refactoring) (0) | 2024.07.20 |
쿠키로 마음을 전하고 싶으면 도메인부터 맞추자 🍪 (0) | 2024.07.20 |