Rxjs-활용기2

in Tech

Deview2016 Angular2 VS React, React VS Angular2 발표에서도
RxJS를 언급했지만, 지금까지도, RxJS를 왜 Angular2가 도입했는가에 대한 이유는 모르겠다.

이를 알아보기 위해,
Angular2 프로젝트를 하면서, RxJS를 가급적 많이 사용하고 있는데,
그 중 하나 느낀 점이 있어 몇자 끄적여 본다.

왜 Angular2는 RxJS를 도입했는가?

일반적으로 Angular2를 사용하게 되면, 화면에 표현되는 요소는 Component로 만들고,
실제 데이터를 가져오는 작업은 Service에서 담당한다.
이 둘의 관계를 RxJS의 Observable 객체를 통해 subscribe로 연결된다.
좀 간단히 보면, 다음 그림과 같다.

Component와 Service 간의 구조에서 RxJS는 둘 사이를 이어주는 다리가 되고,
단방향의 데이터를 가져오는데 결합도가 약하게 연결된다.

이 말을 좀 풀어서 쓰면,
Component에서는 Service를 DI(Dependency Injection)로 주입받고,
주입된 상태에서 데이터를 가져오는 것은 Observable 객체로 연결이 된다는 말이다.
이렇게 되면 Component 입장에서는 주입된 Service의 메소드(foo)만 알면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component({})
class AppComponent extends Component {
// DI로 service 주입
construnctor(service: Service) {
this.service
.foo()
// operator 적용
// filter(...)
// map(...)
// ...
.subscribe(x => {
// 데이터를 받음.
});
}
}

@Injectable()
class Service {
foo(): Observable {}
}

또한, Service에서는 Component와의 연결을 Observable로 사용하기 위해, 메소드의 반환값을 Observable로 사용한다.
반환값이 Observable이기 때문에, RxJS의 operator를 적용하면, Service가 주는 데이터를 변경하거나, 무시하거나, 기타 등등의 작업들을 Component가 할 수 있다.

즉, Angular2에서 RxJS를 사용하면 Component가 갑이되고, Service가 을이 되는 것이다.

예전에는 Service가 주는 데이터만 가지고 처리했다면, 이제는 Component가 데이터의 처리를 결정할수 있게 된 것이다.
흔히 이를 Push방식의 장점이라고 이야기 한다.

그럼 RxJS를 사용하면, 단순히 갑과 을의 관계가 바꿔서 좋다는 말인가? 좋긴하다.
하지만, 그렇다고 이게 RxJS를 사용하는 이유라고 보기에는 납득이 안된다.
더 해봐야겠다.

RxJS와 Typescript 의 캐미

Angular2프로젝트를 하면서 확실하게 안 사실이 있다.
바로 RxJS와 Typescript의 캐미(chemi)이다.

프로젝트를 하다보면 알겠지만, 앞에서 보여준 그림처럼 Component와 Service간의 관계가 1:1인 상황은 많지 않다.
하나의 Component에서 여러개의 Service를 사용하기도 하고, 하나의 Service와 여러개의 Component가 연결되어 있기도 하다.
또한, Service에서 다른 Service와 연결되어 있기도 한다.

Component와 Service간에는 항상 데이터들이 흐르게 되는데. 이때 전달되는 데이터를 따라가기는 쉽지 않다.
물론, 오늘 개발한 코드라면 아주 쉽게 따라 갈수 있겠지만,
개발한 코드가 몇 달 전이거나, 내가 아닌 다른 이가 개발한 코드라면, 데이터 흐름을 따라가는 것은 쉬운일이 아니다.

하지만, 우리는 개발자이니깐 이런 것쯤은 충분히 이겨낼수 있다고 생각한다.
왜? 우린 프로니깐 ㅋㅋ

그런데 문제는 RxJS로 연결 될 경우, 이 Operator에 의해 중간 중간 전달되는 데이터가 변형이 된다는 것이다.
바로, 이런식으로…

1
2
3
4
5
6
7
8
this.service
.foo()
.map(x => {
return somthingFn(x);
})
.subscribe(x => {
// 데이터를 받음.
});

만약, Javascript의 역량을 최대한으로 끌어올려서 데이터를 간결하게(?) 변형(?) 시킨다면,
무슨 데이터가 왔는지 또는 넘겨진 데이터의 속성이 무었인지 확인하기가 어렵다.

하지만. RxJS와 Typescript가 만나면 상황은 좀 달라진다.

1
2
3
4
5
6
7
8
this.service
.foo()
.map((x: Student) => {
return somthingFn(x);
})
.subscribe((x: SmartStudent) => {
// 데이터를 받음.
});

Student 데이터를 받아서, SmartStudent 데이터로 변경했다는 것을 알 수 있다.
한 눈에 데이터가 어떻게 변했는지바로 확인 가능하다.
만약, vscode와 같은 훌륭한 IDE툴이 있다면.데이터의 속성이 또 어떤 것인지 바로 파악할수 있다.

그래서 만약, RxJS를 사용한다면, 꼭! Typescript와 함께하는 걸 추천한다.

Angular2가 RxJS를 왜 도입했는지에 대해서는 아직도 잘 모르겠다.
하지만, Typescript와 RxJS의 캐미가 정말 짱 좋다는 사실은 확인했다.

분명, RxJS와 Angular2의 캐미도 확인할 수 있으니라 본다.ㅋㅋ

Comment and share

Rxjs 활용기1

in Tech

RxJS를 협업 아닌 협업에서 조금씩 쓰고 있다.
오버엔지니어링이라는 생각을 떨쳐버릴 수 없지만, 또 한편으로는 꽤 괜찮다라는 느낌 또한 든다.

현실적인 활용도와 이상적인 개념 사이에서 줄다리기를 하고 있는 느낌이다.

지금은 RxJS의 활용도를 직접 검증도 해보고 싶은 마음도 있고,
간만에 느끼는 개발의 재미도 맛볼 수 있는 영역이라 그 끈을 놓지 않고 있다.

distinctUntilChanged 너는 누구냐?

RxJS의 operator중에 흔하게 쓰이는 distinctUntilChanged 메소드에 대한 약간의 소감을 적어보려고 한다.

http://rxmarbles.com/#distinctUntilChanged

우선 이 함수는 우리가 프로그래밍을 할때 항상 고민하는 일.
중복호출? 중복 전달 등에 대한 문제를 아주 손쉽게 해결해 준다.

distinctUntilChanged 함수는 기본적으로 distinctUntilChanged()를 호출해서 사용 할 수 있다.

1
2
3
Rx.Observable.of(10, 30, 30, 30)
.distinctUntilChanged()
.subscribe(x => console.log(x));

결과는 예상했던 것처럼, 10, 30 만 나온다. 좋다. 쉽다!

Rxjs 버전마다 달라요~

하지만, 다음과 같은 코드인 경우, rxjs의 버전에 따라 다르게 동작한다.

1
2
3
Rx.Observable.of({ value: 10 }, { value: 30 }, { value: 30 }, { value: 30 })
.distinctUntilChanged()
.subscribe(x => console.log(x));

위의 코드는 rxjs4에서는 정상 동작하지만, rxjs5에서는 primitive 타입이 아닌 경우에는 정상적으로 동작하지 않는다. 된장!

이렇게 되는 이유는 rxjs의 기본 compare 함수가 rxjs4와 rxjs5가 다르기 때문이다.

rxjs4의 기본 compare함수는 다음과 같다.

https://github.com/Reactive-Extensions/RxJS/blob/master/dist/rx.all.js#L554-L562

1
2
3
4
5
6
7
8
9
function baseIsEqual(value, other, ...) {
if (value === other) {
return true;
}
if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
return value !== value && other !== other;
}
return baseIsEqualDeep(value, other, baseIsEqual, ...);
}

기본적으로 값이나 reference 값을 비교하고, 만약, reference 값이 같다면, 깊은 탐색을 한다.

반면, rxjs5의 기본 compare 함수는 다음과 같다.

https://github.com/ReactiveX/rxjs/blob/master/src/operator/distinctUntilChanged.ts#L53-L55

1
2
3
private compare(x: any, y: any): boolean {
return x === y;
}

값이나 reference 값을 비교하는 간단한 함수이다.

이렇게 한 이유는 rxjs5가 추구하는 Better performance를 위한 조치로 보인다. 하지만…. 사용자는 헷갈린다.

그래서 rxjs5에서는 primitive 타입이 아닌 경우에는 immutable한 객체를 데이타로 활용 해야 효과적이다.

즉, 객체의 reference가 변경되면, 값이 변경되었고, 그렇지 않으면 변경되지 않았다는 대전제가 수반되어야한다.

객체를 매번 immutable하게 만들려고 하면 결국에는 복사라는 불필요한 과정을 거쳐야만 한다. 비교하고자 하는 객체가 크면 클수록 그 비용은 더 크다.
모델 자체가 간단할 때는 크게 문제가 되지 않는다. 하지만, 좀 큰 모델 객체가 변경되었을 때는 어떻게 하는게 좋을까?
물론, immutable.js 같은 라이브러리를 사용해서 그 비용을 줄있 수는 있다.
하지만, 역시 비용은 비용이다.

distinctUntilChanged를 잘 사용해보자.

rxjs5의 distinctUntilChanged는 compare함수와 keySelector 함수를 파라미터로 받아 들어서 보다 효과적으로 처리할 수 있다. 물론, rxjs4도 가능하다. 다만, 파라미터의 순서가 다르다.

1
2
3
4
5
// rxjs4
distinctUntilChanged(keySelector: function, comparer: function);

// rxjs5
distinctUntilChanged(compare?: (x: K, y: K) => boolean, keySelector?: (x: T) => K)

compare 함수는 이전 키와 이후 키를 비교 하는 함수이고,
keySelector 함수는 비교할 키가 무엇인지를 명시하는 함수이다.
rxjs5에서는 다음과 같이 작성하면 이러한 문제를 손쉽게 해결 할 수 있다.

1
2
3
Rx.Observable.of({ value: 10 }, { value: 30 }, { value: 30 }, { value: 30 })
.distinctUntilChanged((p, n) => p === n, x => x.value)
.subscribe(x => console.log(x));

결과는 예상했던 것처럼, {value:10}, {value:30} 만 나온다. 역시나, 좋다.

keySelector를 이용한 꽁수

위와 같이 키(value)가 명확하다면, 문제가 되지 않지만, 만약, 키로 정할 값이 명확하지 않고, 모델의 구조가 복잡하다면, keySelector 함수를 구현하기가 어렵다.

이런 경우는 개발 꽁수 이지만, 키를 자체적으로 만들어서 사용하는 방법이 있다.

예를 들어, 여러 셀들을 포함하는 spreadsheet 모델이 다음과 같고, rows안의 특정 cell의 값(value)이나, cell의 개수만 변경되는 경우라면, 사용자 고유키를 만들수 있다.

1
2
3
4
5
6
7
spreadsheet = {
rows: [
{ row: 0, col: 0, value: "A" },
{ row: 0, col: 1, value: "B" },
{ row: 0, col: 2, value: "C" }
]
};

다음과 같이 row의 length와 cell의 값(value)으로 임의의 고유키를 만들수 있다.

1
2
3
4
5
6
function(x) {
return x.rows.length + "_" + x.rows.reduce( (acc, v) => {
acc.push(v.value);
return acc;
}, []).join("_");
}

immutable을 사용하는 것도 좋은 방법이지만, 이런 개발 꽁수를 이용하면, 복사라는 비용없이 객체의 변경여부를 확인할 수 있다.

실제 생성된 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Rx.Observable.of(
{
rows: [
{ row: 0, col: 0, value: "A" },
{ row: 0, col: 1, value: "B" },
{ row: 0, col: 2, value: "C" }
]
},
{
rows: [
{ row: 0, col: 0, value: "A" },
{ row: 0, col: 1, value: "B" },
{ row: 0, col: 2, value: "C" }
]
},
{
rows: [
{ row: 0, col: 0, value: "A" },
{ row: 0, col: 1, value: "B_changed" },
{ row: 0, col: 2, value: "C" }
]
}
)
.distinctUntilChanged(null, x => {
return (
x.rows.length +
"_" +
x.rows
.reduce((acc, v) => {
acc.push(v.value);
return acc;
}, [])
.join("_")
);
})
.subscribe(x => console.log(x));

https://jsbin.com/soluwozoji/edit?js,console

Comment and share

  • page 1 of 1
Author's picture

sculove

아내와 아들 그리고 딸밖에 모르는 남편


FrontEnd Developer


대한민국/서울