4주차 발표자료 전형진 - TECH-SHARING-STUDY/FE_STUDY GitHub Wiki
RxJS가 무엇인지 알아보기 전에 간단하게 어떤 문제를 해결하고자 RxJS라는 라이브러리가 탄생했는지 살펴보겠습니다.
비동기 코드가 많아진다는 것은 프로그램의 흐름이 복잡해지고 예측하기 어려워진다는 뜻입니다. 그 말은 곧 버그로 이어질 가능성이 높아진다는 뜻입니다.
그리고 어떤 상황에서는 동기로 데이터를 처리하고, 어떤 상황에서는 비동기로 데이터를 처리합니다. 각 방식을 다루는 코드 구조가 다르고, 이러한 코드들이 혼재되어 프로그램을 더욱 복잡하게 만듭니다.
웹 애플리케이션의 주요한 과제 중 하나는 상태 관리라 볼 수 있습니다. 상태가 어떤 이벤트로 인해 변했다면, 이를 의존하고 있는 모든 요소들에게 그 사실을 전파하는 것은 필수적이다.
하지만 애플리케이션이 복잡해지면 상태 변화에 따른 전파를 제대로 처리하지 않아 오류가 될 가능성이 높아집니다.
단순한 비즈니스 로직을 구현할 때는 분기문이나 반복문이 별로 없기 때문에 그렇게 문제가 되지 않습니다.
하지만 비즈니스 로직이 복잡해지면, 수많은 분기문과 반복문들을 사용하면서 내부의 복잡도는 크게 증가합니다. 거기에 방어적인 코드를 위해 try...catch 구문까지 쓴다면 더더욱 복잡해질 것입니다. 만약 외부 변수까지 사용한다면, 사이드 이펙트까지 발생할 것입니다.
RxJS is a library for composing asynchronous and event-based programs by using observable sequences
공식문서에서 RxJS는 Observable을 사용하여 비동기와 이벤트 기반의 프로그램을 작성하기 위한 라이브러리이다.
라고 정의되어 있습니다. 이 말은 비동기든 이벤트든, 혹은 일반 컬렉션의 데이터든 모두 Observable이라는 하나의 타입을 통해 다룬다는 의미입니다.
간단한 예를 들어보겠습니다. DOM의 click 이벤트를 Observable로 어떻게 다루는 지 살펴봅시다.
const button = document.querySelector("button");
button.addEventListener("click", (event) => {
console.log("Hello, World!");
})
웹에서 DOM 이벤트를 처리하기 위해서는 위와 같이 addEventListener
메소드를 통해 이벤트 핸들러를 등록하여 처리합니다.
const { fromEvent } = rxjs;
const { map, tap } = rxjs.operators;
const button = document.querySelector("button");
const click$ = fromEvent(button, "click");
click$.subscribe(x => console.log("Hello, World!"));
반면, RxJS에서는 fromEvent
라는 함수를 통해 DOM 이벤트를 Observable로 만듭니다. 위 예제에서는 button의 클릭 이벤트를 Observable로 변환하여 click$
에 할당합니다. 이로써, 클릭 이벤트는 데이터 소스가 되어 이벤트가 발생할 때마다 event 객체를 공급해주는 Observable이 됩니다. (변수명 마지막에 붙은 $
은 해당 변수가 Observable임을 의미하는 컨벤션입니다.)
공급해준 데이터를 소비할 방법이 있어야겠죠? Observable의 subscribe
메소드에 Observer를 넘기면, 해당 Observable이 공급한 데이터를 Observer가 소비할 수 있습니다. Observer는 객체나 함수가 될 수 있으며, 위의 경우는 Observer는 Hello, World
문자열을 콘솔에 출력하는 함수가 되겠습니다.
방금 예제에서 RxJS의 중요한 개념 2가지가 나왔습니다. 바로 Observable과 Observer 입니다. 이번에는 이들과 더불어 중요한 개념 몇 가지를 더 살펴볼 것입니다.
- Observable
- Operator
- Observer
- Subscription
일정 시간동안 발생하는 일련의 데이터들을 스트림(Stream)이라 하는데, 스트림을 이루는 데이터들을 관리하고 이를 Observer에게 전달하는 역할을 담당합니다.
0123
위 그림을 보면, 일정 시간동안 간격을 두고 0부터 3까지 숫자를 생성되고 생성될 때마다 Observable은 생성된 값을 Observer에게 전달합니다.
오퍼레이터는 Observable을 생성하거나 조작하는 함수를 일컫습니다. 오퍼레이터를 통해 현재 Observable가 다루는 데이터를 조작할 수 있고, 각각의 Observable을 연결지을 수도, 분리하거나 합칠 수도 있습니다.
대표적인 오퍼레이터로,
- of, from, fromEvent, ... => Observable을 생성하는 오퍼레이터
- map, filter, reduce, find, ...
두 번째 분류에 해당하는 오퍼레이터들은 Pipeable Operator라 불리며, Observable을 입력을 받아 해당 Observable의 값을 불변하게 다루고 새로운 Observable을 반환하는 순수함수입니다.
방금 언급한 오퍼레이터말고도 수 많은 Pipeable Operator가 존재하고, 이들을 사용하기 위해서는 시간이 흘러감에 따라 어떻게 데이터를 변형하는지 알아야 합니다. 이를 위해 각 오퍼레이터의 동작방식을 시각적으로 표현한 마블 다이어그램(marble diagrams)을 사용합니다.
마블 다이어그램은 시간에 따른 데이터의 흐름을 추상화한 도표입니다. 이를 제대로 볼 줄 알아야 Observable이 구독될 때, 해당 Observable이 Observer에게 제공하는 데이터의 흐름을 정확히 파악할 수 있습니다.
위 다이어그램을 해석하기 전에, 마블 다이어그램을 이루는 요소들을 잠시 설명하겠습니다.
-
가로로 뻗어나가는 선은 Observable의 시간 축을 의미합니다. 즉, 시간의 흐름을 나타냅니다.
-
선 위에 존재하는 동그라미는 시간에 따라 Observable이 Observer에게 제공할 데이터를 의미합니다.
-
세로 선은 Observable이 제공할 데이터들을 성공적으로 제공(완료)했음을 나타내는 선입니다.
-
만약 도중에 오류가 발생하여 제대로 값을 전달하지 못할 경우, X 를 표시합니다. 이 이후로는 값이 전달되지 않습니다.
-
가운데 박스를 기준으로 위에 위치한 Observable을 입력 Observable라 하고, 아래 위치한 Observable을 오퍼레이터가 적용된 출력 Observable라 합니다.
- 가운데 박스 안에 명시된 텍스트 적용할 오퍼레이터의 이름입니다.
이를 바탕으로 다이어그램을 해석하면,
- 입력 Observable은 4, 6, a, 8를 순서대로 데이터를 공급하고, 정상적으로 완료합니다.
- 입력 Observable이 공급한 데이터에
mulitplyByTen
이라는 오퍼레이터가 적용되어 새로운 Observable을 만듭니다. - 출력 Observable은 입력 Observable이 제공한 값에 10배를 곱하여 Observer에게 값을 전달합니다. 정상적으로 처리하다가 a를 10배 곱할 때 에러가 발생합니다. 에러 상황을 전달하고 종료하며, 더 이상 데이터를 전달하지 않습니다.
Observable이 데이터를 공급하면 이를 소비하는 주체입니다. Observer는 next
, error
, complete
함수를 가진 객체를 의미합니다.
- next : 정상적으로 데이터가 전달되었을 때 호출되는 함수
- error : 에러가 발생했을 때 호출되는 함수 ()
- complete : 데이터 전달이 완료되었을 때 호출되는 함수
// 1. 객체를 전달하는 방법
const observer = {
next: x => console.log('정상적으로 값을 받았습니다.', x),
error: err => console.log('에러가 발생했습니다.', err),
complete: () => console.log('정상적으로 Observable이 완료되었습니다.')
};
observable$.subscribe(observer);
// 2. 콜백 형태로 전달하는 방법
observable$.subscribe(
x => console.log('정상적으로 값을 받았습니다.', x),
err => console.log('에러가 발생했습니다.', err),
() => console.log('정상적으로 Observable이 완료되었습니다.')
);
Subscription 객체는 Observable이 구독되었음을 나타내는 객체입니다. subscribe 메소드가 반환하는 객체이며, 이 객체의 주요 역할은 구독 해지(unsubscription)입니다.
구독한 Observable의 데이터를 더 이상 전달받고 싶지 않을 경우, 해당 객체의 unsubscribe
메소드를 호출하여 구독을 해지하면 됩니다.