Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxJS - The Art of Abstraction

9b753d898d93aae8bd163db5c420a1ae?s=47 Jerry Hong
November 01, 2019

RxJS - The Art of Abstraction

9b753d898d93aae8bd163db5c420a1ae?s=128

Jerry Hong

November 01, 2019
Tweet

Transcript

  1. RxJS The Art of Abstraction

  2. Jerry Hong Tech Leader | Website: blog.jerry-hong.com Facebook: J.H.blog.tw Branch8

    2019 ModerWeb speaker 2018 FEDC organiser 2017 JSDC.tw speaker 2017 RxJS 30天鐵⼈人賽冠軍 2016 JSDC.tw speaker
  3. What’s RxJS ?

  4. Lodash for async

  5. ⼀一個透過 Observable 組合各種非同步⾏行行為的 Library

  6. Why RxJS ?

  7. let isRequesting = false; scrollView.addEventListener('scroll', event => { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } });
  8. let isRequesting = false; scrollView.addEventListener('scroll', event => { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } }); 無限滾動 Infinite scroll
  9. let isRequesting = false; scrollView.addEventListener('scroll', event = const DOM =

    event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; 註冊滾動事件 Listen scroll event
  10. let isRequesting = false; scrollView.addEventListener('scroll', event = const DOM =

    event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); 判斷滾動⾼高度 Determine scroll height
  11. scrollView.addEventListener('scroll', event = const DOM = event.target; if(hasScrolled(DOM) > 0.9

    && !isRequesting) isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } }); 發送 Request
  12. 設定 Flag let isRequesting = false; scrollView.addEventListener('scroll', event = const

    DOM = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) isRequesting = true; fetch('url...') .then(res => {
  13. let isRequesting = false; scrollView.addEventListener('scroll', event = const DOM =

    event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); 若若 isRequesting 為 false 
 再發送 Request
  14. let isRequesting = false; scrollView.addEventListener('scroll', event = const DOM =

    event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } 發送 Request 前, 設定 isRequest 為 true
  15. const DOM = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) isRequesting

    = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } }); Response 後, 設定 isRequest 為 false
  16. let isRequesting = false; scrollView.addEventListener('scroll', event => { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } }); 無限滾動 Infinite scroll
  17. What’s wrong ?

  18. let isRequesting = false; scrollView.addEventListener('scroll', event => { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } }); 兩兩個非同步⾏行行為
  19. let isRequesting = false; scrollView.addEventListener('scroll', event => { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } }); 同樣是非同步⾏行行為
 卻⽤用不同的 pattern
  20. Flag let isRequesting = false; scrollView.addEventListener('scroll', event => { const

    DOM = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; }); } });
  21. More Flags let isRequesting = false; let requestCount = 0;

    const scrollHandler = function(event) { const DOM = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; requestCount = requestCount + 1; if (requestCount === 3) { scrollView.removeEventListener('scroll', scrollHandler) } }) } } scrollView.addEventListener('scroll', scrollHandler);
  22. let isRequesting = false; let requestCount = 0; const scrollHandler

    = function(event) { const DOM = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; requestCount = requestCount + 1; if (requestCount === 3) { scrollView.removeEventListener('scroll', scrollHandler) } }) } } scrollView.addEventListener('scroll', scrollHandler); 我們⼀一定要寫
 這麼醜的程式碼嗎?
  23. let isRequesting = false; let requestCount = 0; const scrollHandler

    = function(event) { const DOM = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) { isRequesting = true; fetch('url...') .then(res => { // do something change view isRequesting = false; requestCount = requestCount + 1; if (requestCount === 3) { scrollView.removeEventListener('scroll', scrollHandler) } }) } } scrollView.addEventListener('scroll', scrollHandler); fromEvent(scrollView, 'scroll') .pipe( map(event "=> event.target), map(hasScrolled), filter(p "=> p > 0.9), exhaustMap(() "=> ajax('url""...')), take(3) ) .subscribe(res "=> { "// do something change view }) VanillaJS RxJS
  24. Observable 簡介 Observable 簡介

  25. What is Observable ?

  26. What is Observable ? Collection over Time

  27. Observable 就像是⼀一個序列列,
 裡⾯面的元素會隨著時間推送 What is Observable ?

  28. var mouseMove = fromEvent(DOM, 'mousemove'); observable 建立與訂閱 var subscription =

    mouseMove .subscribe(x => console.log(x)); subscription.unsubscribe();
  29. of(2, 3, 4); from([2, 3, 4]); ajax('url'); fromEvent(DOM, 'click'); interval(1000);

    建立 observable
  30. import { from } from 'rxjs'; import { map, filter

    } from 'rxjs/operators'; var sub = from([1, 2, 3]) .pipe( map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {}, complete: () "=> {}, });
  31. observable • Observable 的物件實例例 • 在未被訂閱之前,只是 個物件,不會有任何 Side Effect •

    可被訂閱(subscribe) import { from } from 'rxjs'; import { map, filter } from 'rxjs/operators var sub = from([1, 2, 3]) .pipe( map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {},
  32. operator • ⼀一個 function 傳入 observable 回傳 observable • 可對元素作運算處理理

    import { from } from 'rxjs'; import { map, filter } from 'rxjs/operators var sub = from([1, 2, 3]) .pipe( map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {}, complete: () "=> {}, });
  33. observer • ⼀一個 object • 具有 next, error, complete •

    ⽤用來來訂閱 observable map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {}, complete: () "=> {}, });
  34. subscription • observable 訂閱後回傳 的物件 • 可拿來來退訂 (unsubscribe) • 可以其他

    subscription 合併 import { from } from 'rxjs'; import { map, filter } from 'rxjs/operators var sub = from([1, 2, 3]) .pipe( map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {}, complete: () "=> {}, });
  35. None
  36. Marble Diagram --a--b--c--d--e|

  37. Marble Diagram - ⼀一⼩小段時間 (10 frames) n(0-9/a-z) 送出的元素(next) | 送出結束

    (complete) # 送出錯誤 (error) () 同步送出 time ----0---1---2---3-- ----0---1---2---3| ----0---1---2---3--# (123|)
  38. of(1, 2, 3) (123|)

  39. interval(10) - -012 -01 -0 -0123 -01234 -01234..

  40. fromEvent(DOM, 'click') ---e--ee-e--e-...

  41. interval(10) .pipe( take(3), map(x "=> x + 1), filter(x "=>

    x % 2 ""=== 1) ) -01234.. -012| -1-(3|) -01(2|) -12(3|)
  42. --0-1-2-3-4-5-6-7.. -------e----------- --0-1-2| interval(20) .pipe( takeUntil( fromEvent(DOM, 'click') ) )

  43. fromEvent(DOM, 'click') .pipe( map(() "=> ajax('url""...')) mergeAll() ) -----e-e---- -----o-o----

    \ \ \ ----r| ----r| ---------r-r-
  44. fromEvent(DOM, 'click') .pipe( mergeMap(() "=> ajax('url""...')) ) -----e-e---- -----o-o---- \

    \ \ ----r| ----r| ---------r-r-
  45. fromEvent(DOM, 'click') .pipe( switchMap(() "=> ajax('url""...')) ); -----e-e---- -----o-o---- \

    \ \ ----r| ----r| -----------r-
  46. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  47. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  48. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  49. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  50. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  51. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  52. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  53. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  54. -----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  55. -----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  56. -----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  57. -----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  58. -----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  59. -----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(

    switchMap(() "=> ajax('url""...')) );
  60. -----e-e---- -----o-o---- \ \ \ ----r| --! -----------r- fromEvent(DOM, 'click')

    .pipe( switchMap(() "=> ajax('url""...')) );
  61. fromEvent(DOM, 'click') .pipe( exhaustMap(() "=> ajax('url""...')) ) -----e-e---- -----o-o---- \

    \ \ ----r| ----r| ---------r---
  62. fromEvent(scrollView, 'scroll') .pipe( map(event "=> event.target), map(hasScrolled), filter(p "=> p

    > 0.9), exhaustMap(() "=> ajax('url""...')), take(3) ) .subscribe(res "=> { "// do something change view })
  63. Change Your Thoughts

  64. Before You Learn RxJS

  65. Make a HTTP request Listen an event
 
 Read a

    file Promise Callback Stream
  66. After You Learn RxJS

  67. Make a HTTP request Listen an events
 
 Read a

    file A set of values A set of values A set of values
  68. A set of values A set of values A set

    of values Observable Make a HTTP request Listen an events
 
 Read a file
  69. What’s matter?

  70. 物件拖拉 Drag&Drop Drag Drop

  71. mouseDown$

  72. mouseDown$ mouseDown$.pipe( switchMap(() "=> mouseMove$)) )

  73. mouseDown$.pipe( switchMap(() "=> mouseMove$)) )

  74. mouseDown$.pipe( switchMap(() "=> mouseMove$.pipe(takeUntil(mouseUp$)) ) )

  75. mouseDown$.pipe( switchMap(() "=> mouseMove$.pipe(takeUntil(mouseUp$)) ) ) .subscribe(value "=> { "//

    do something });
  76. None
  77. mouseClick$

  78. mouseClick$.pipe( switchMap(() "=> request$) ) mouseClick$

  79. mouseClick$.pipe( switchMap(() "=> request$) )

  80. mouseClick$.pipe( switchMap(() "=> request$.pipe(takeUntil(cancel$)) ) )

  81. mouseClick$.pipe( switchMap(() "=> request$.pipe(takeUntil(cancel$)) ); ); .subscribe((value) "=> { "//

    do something })
  82. mouseClick$.pipe( switchMap(() "=> request$.pipe(takeUntil(cancel$)) ); ); .subscribe((value) "=> { "//

    do something })
  83. mouseClick$.pipe( switchMap(() "=> request$.pipe(takeUntil(cancel$)) ); ); .subscribe((value) "=> { "//

    do something }) mouseDown$.pipe( switchMap(() "=> mouseMove$.pipe(takeUntil(mouseUp$)) ) ) .subscribe(value "=> { "// do something }); 取消請求 Cancel Request 拖拉 D&D
  84. Same Logic, Same Code

  85. Everything is set of values

  86. HTTP Server

  87. Requests -> Responses

  88. None
  89. const helloEffect$: HttpEffect = req$ "=> req$.pipe( mapTo({ body: 'Hello,

    world!' }), );
  90. const postUser$ = r.pipe( r.matchPath('/user'), r.matchType('POST'), r.useEffect(req$ "=> req$.pipe( map(req

    "=> req.body as User), mergeMap(Dao.postUser), map(response "=> ({ body: response })) )));
  91. User Actions

  92. Action -> Side Effects

  93. None
  94. const pingEpic = action$ "=> action$.pipe( filter(action "=> action.type ""===

    'PING'), delay(1000), "// Asynchronously wait 1000ms then continue mapTo({ type: 'PONG' }) );
  95. const incrementIfOddEpic = (action$, state$) "=> action$.pipe( ofType(INCREMENT_IF_ODD), filter(() "=>

    state$.value.counter % 2 ""=== 1), map(() "=> increment()) );
  96. Rx is cross languages!

  97. RxJava RxSwift RxScala RxClojure RxGo RxPy

  98. Thanks

  99. Any Questions?