RXjs
RXjs를 사용해보자.
제가 스크롤 이벤트에 의한 데이터 처리를 해야하는 개발 작업을 담당 받았을때 스크롤 === 시간 ==== 데이터와 같이 치환해서 작업해야 하는 상황이 생겼습니다. 이 때 해결을 위해 RXjs를 사용했고 그 이유와 사용 방법에 대해서 간단하게 작성합니다.
안타깝게도 스크롤은 이벤트 입니다. 그리고 시간이라는 비동기라는 어려운 조건이 포함되어 있습니다. 비동기 데이터를 동기적으로 처리해야 할때 보통 Promise, Async await 구문을 사용하는데, 하지만 이렇게 하더라도 복잡한 이벤트 동작 마다 처리해 줘야하는 코드의 복잡도가 높아지고, 이벤트를 하나로 합치거나 시간에 흐름 혹은 스크롤 위치 등에 따른 정의를 해주기가 쉽지 않았습니다.
스크롤 동작은 혹은 크게 봤을때 이벤트는 유저의 설정에 따라 환경에 따라 너무 느리거나 빠를 수도 있습니다. 이런 문제는 기존의 비동기 처리 방식인 callback 혹은 promise로는 쉽지 않습니다.
RX는 이런 복잡한 비동기적 이벤트들을 데이터의 흐름으로 처리 할수 있게 해줍니다.
그리고 데이터를 통한 업데이트를 구현하는 것도 데이터의 형태에 따른 UI의 변화이기 때문에 매우 간단하게 이런 문제를 해결 할 수 있습니다.
해당 문제들을 해결하는 방식 예시
const observeEvent = (target, type) => new Observable(observer => {
target.addEventListener(type, (event) => observer.next(event))
return () => target.removeEventListener(type)
})
const tripleClicks$ = observeEvent(window, "click")
.bufferTime(1000) // 1초간
.filter(clicks => clicks.length > 5) // 클릭이 5번
.subscribe(...)
리액티브 프로그래밍
처음에는 이 개념에 대해서 들었을때는 프론트엔드에서 과연 사용할 만한 일이 있을까? 라는 생각을 했지만 비동기 처리의 문제를 해결하기 위해서 가장 쉬운 방법이란 것을 깨닫고 나서는 매우 유용하다고 생각하게 되었습니다.
결국 우리는 데이터와 그 데이터의 형태 그리고 데이터가 어떻게 처리되는지를 제어하는게 프로그래밍이고, 리액티브 프로그래밍은 데이터의 형태와 데이터의 처리가 하나의 스트림으로 되어 있고, 스트림에 대한 제어로 생각하면 문제 해결이 쉬워 진다는 생각을 할 수 있습니다.
프론트엔드가 어려워 지는 순간이 언제인지 생각해보면 결국 비동기적인 동작에 대한 순서를 제어해야 할 때 입니다. 동시성 프로그래밍을 구현하기 위해서는 생각보다 복작한 로직이 필요합니다.
채팅 어플리케이션의 경우 내가 채팅을 치는 비동기 적인 작업, 사용자가 채팅을 쳐서 해당 채팅 메세지가 서버에서 넘어오는 비동기적인 작업, 스크롤 동작에 의해서 채팅 화면이 업데이트 해야 되는 비동기적인 작업등이 있습니다.
해당 비동기 작업이 어떤 비동기 작업에 의존적이고 동기적으로 동작하도록 해야 하는지 이런 제어를 흐름으로 생각하게 되면 비동기 로직등도 함수형으로 작성 가능해서 코드가 이해하기 쉬워 지고 다른 사이드 이팩트가 일어나지 않게 작성할 수 있습니다.
채팅 어플리케이션의 경우 여러개의 데이터를 병합해서 한번 업데이트를 해주거나, 내가 채팅을 입력한 시점 그전에 일어난 채팅의 업데이트 이후에 내 채팅을 업데이트 해줘야하는 상황이 자주 일어나게 됩니다.
예시 코드
// 모든 채팅 이벤트를 하나의 스트림으로 관리
chatEventStream
.pipe(
scan(
(acc, event) => {
// 이벤트에 따라 상태 업데이트 (예: 채팅 목록, 읽음 상태 등)
switch (event.type) {
case "message":
return {
...acc,
messages: [...acc.messages, event.data],
};
case "read":
// ... 읽음 상태 업데이트 로직
return {
...acc,
readMessages: [...acc.readMessages, event.data.messageId],
};
default:
return acc;
}
},
{ messages: [], readMessages: [] }
),
map((state) => {
// 필요한 데이터 추출 (예: 최근 메시지 10개)
return state.messages.slice(-10);
})
)
.subscribe((messages) => {
// UI 업데이트
console.log("최근 메시지:", messages);
});
채팅 앱등 비동기 처리가 복잡한 경우가 아니라면 꼭 RXjs를 사용할 필요는 없지만 알아 둔다면 매우 유용한 기술이라고 생각합니다.