링크 : https://www.yalco.kr/@rxjs/3-1/
소개 : 사용자의 입력에 따라 AJAX 호출로 서버에서 사용자 이름을 조회하는 기능이다.
계획(initial plan) : 기능 구현을 위해 단계별로 생각해보기. 작업 전 1차적으로 구상해보고 작업 완료 후 1차에서의 계획이 그대로 실현되었는지 생각해보고 다시 정리해 보기.
단계 : ①키 이벤트(입력) → ②Ajax 호출(검색) → ③결과 하단에 출력
사용될 연산자 : mergeMap, switchMap, pluck, filter, debounceTime, distinctUntilChange
■ mergeMap
const url = 'http://127.0.0.1:3000/people/quarter-error'
const keyword = document.querySelector('#keyword')
const result = document.querySelector('#result')
fromEvent(keyword, 'keyup').pipe(
pluck('target', 'value'),
mergeMap(keyword =>
ajax(`${url}?name=${keyword}`).pipe(retry(3))
),
pluck('response')
).subscribe(console.log)
mergeMap의 문제점 :
1. 키를 입력할 때 마다 ajax 호출이 일어나고 그에 따른 결과값을 받아오기 때문에 아래 그림과 같이 키를 하나씩 입력시 자동완성탭이 수시로 바뀌는걸 볼 수 있다.
2. 불필요한 ajax 요청에 의해 서버에 부담을 줄 수 있다.
■ switchMap
const url = 'http://127.0.0.1:3000/people/quarter-error'
const keyword = document.querySelector('#keyword')
const result = document.querySelector('#result')
fromEvent(keyword, 'keyup').pipe(
pluck('target', 'value'),
// meregeMap 대신 switchMap 사용
switchMap(keyword =>
ajax(`${url}?name=${keyword}`).pipe(retry(3))
),
pluck('response')
).subscribe(showResults)
function showResults (results) {
from(results).pipe(
map(person => `${person.first_name} ${person.last_name}`),
map(name => `<article>${name}</article>`),
scan((acc, article) => acc += article, '')
).subscribe(people => result.innerHTML = people)
}
1번 문제를 해결하기 위해 switchMap을 사용할 수 있다.
switchMap은 메인 스트림이 새 값을 발행하면 진행중이던 스트림을 멈추는 기능을 한다.
c를 입력 후 response가 도착하기 전에 h를 연달아 입력하면 c를 입력했을때의 ajax 요청이 멈추고 ch로 새로운 스트림을 만들어 ajax를 호출하는 것이다. 그리고 이렇게 발생된 모든 스트림들을 병합한다.
즉, 키가 입력될 때 마다 이전 ajax호출을 멈주는 것이다.
2번 문재를 해결하기 위해 pluck, filter, debounceTime, distinctUntilChange를 사용한다.
fromEvent(keyword, 'keyup').pipe(
filter(event => event.code != 'Backspace'), // 백스페이스 생략
pluck('target', 'value'),
filter(typed => typed.length > 1),
debounceTime(500),
distinctUntilChanged(),
switchMap(typed =>
ajax(`${url}?name=${typed}`).pipe(retry(3))
),
pluck('response')
).subscribe(showResults)
pipe에서 가공되는 과정은 아래와 같다...
1단계 filter - input 박스에 백스페이스키 입력을 제한한다.
2단계 pluck - input 박스에 입력된 값을 받아온다.
3단계 filter - input 박스의 문자열 길이가 하나 이상이어야 한다.
4단계 debounceTime - input 박스에 키를 입력할때 마다 0.5초의 시간을 재도록 한다. 그래야 input 박스에 키를 연속적으로 입력했을 때 마다 ajax 호출이 일어나지 않게 된다.
5단계 distinctUntilChanged - 사용자의 실수로 연속적인 알파벳을 입력했을때를 대비해 같은 문자가 연속적으로 입력되지 않도록 한다.
6단계 switchMap - 4단계에서 역속적인 입력 이벤트를 방지해주지만 switchMap으로 다시 한번 예방할 수 있다.
이로써 기능은 모두 완성되었다.
리팩토링을 조금 해보면, 기능의 성격에 따라 분리해보자.
추가 - ajax에 대한 ajax의 response가 오기 전 "searching 문구 출력"
스트림 분리1 - input 박스에서의 키입력
스트림 분리2 - 입력값에 따라 ajax 호출
마지막으로 두 개의 스트림(분리1, 분리2)을 병합 → 결과값을 HTML에 노출
// 추가
const searching$ = searchInit$.pipe(
mapTo('<div class="searching">Searching...</div>')
)
// 분리1
const searchInit$ = fromEvent(keyword, 'keyup').pipe(
filter(event => event.code != 'Backspace'), // 백스페이스 생략
pluck('target', 'value'),
filter(typed => typed.length > 1),
debounceTime(500),
distinctUntilChanged()
)
// 분리2
const searchResult$ = searchInit$.pipe(
switchMap(keyword =>
ajax(`${url}?name=${keyword}`).pipe(retry(3))
),
pluck('response'),
mergeMap(results => from(results).pipe(
map(person => `${person.first_name} ${person.last_name}`),
map(name => `<article>${name}</article>`),
scan((acc, article) => acc += article, '')
))
)
// 두 개의 스트림 병합
merge(
searching$,
searchResult$
).subscribe(text => result.innerHTML = text)
처음 구상했던 계획은 아래와 같다.
①키 이벤트(입력) → ②Ajax 호출(검색) → ③결과 하단에 출력
그렇다면 처음 계획했던 내용과 무엇이 수정되고 달라졌는지 보자.
①키 이벤트(입력)
단순히 입력만 하는 것만 고려할 것이 아니라 이벤트 처리과정에 대해 생각해 볼 필요가 있었다. mergeMap을 사용했을 때 불필요한 Ajax 호출이 키를 입력할 때 마다 일어났었고, 이를 해결하기 위해 switchMap, debounceTime 연산자를 사용해 문제를 해결했다.
사용자는 입력뿐만 아니라 입력한 내용을 지울 수도 있다라는 사용자관점을 생각하지 못했다. 백스페이스를 눌렀을때 어떻게 처리할지 생각하지 못했다. 우선 filter 연산자를 통해 키 코드가 backspace인 것을 골라내었다.
②Ajax 호출(검색)
성공, 실패 각각 결과에 따라 어떻게 처리할 것인지 생각했다. retry 연산자로 ajax요청 실패 시 최소 3번은 다시 시도할 수 있도록 한다.
결론
크게 수정되거나 달라진 것은 없지만, 전반적인 "처리과정"에 대해 머릿속으로 설계도를 그리는 연습을 해야할 것 같고, 사용자관점에서도 조금 더 깊게 생각하면 좋을 것 같다.
'RxJS(Reactive X)' 카테고리의 다른 글
routerLink로 이동 후 service에 저장된 Subject를 구독하지 않는 상황. (구독시점에 대한 이해) (1) | 2023.11.23 |
---|---|
servie.ts 에서 모든 구독자에게 동일한 값을 발행할때 (0) | 2023.07.27 |
[Chapter#2] RxJS 연산자들 -ing (0) | 2023.01.13 |
[Chapter#1] Lesson 4. 내맘대로 발행하는 Subject (0) | 2023.01.12 |
[Chapter#1] Lesson 3. Operator 사용해보기 (0) | 2023.01.12 |
댓글