├── .gitbook.yaml ├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── README.md ├── SUMMARY.md ├── alipay.jpg ├── book.json ├── concepts ├── observable.md ├── observer.md ├── operators.md ├── schedulers.md └── subject.md ├── debugger ├── test.md └── tools.md ├── demos ├── box-drag-and-move.md ├── clickclickclick.md ├── eggs.md ├── medias │ └── race_promise_jsonp.png ├── race.md ├── search-github-users.md └── thrice-click.md ├── frameworks ├── rxjs-with-Angular │ └── README.md └── rxjs-with-React │ ├── README.md │ └── assets │ ├── redux.jpg │ └── rxjs.jpg ├── literature.bib ├── main.md ├── operators ├── README.md ├── combine operators │ └── README.md ├── createion operators │ ├── README.md │ ├── ajax.md │ ├── from.md │ └── of.md ├── custom operator │ └── README.md ├── filters operators │ └── README.md ├── multi operators │ └── README.md └── transform operators │ ├── README.md │ ├── all-maps.md │ └── scan-vs-reduce.md └── wechat.jpg /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: . 2 | 3 | structure: 4 | readme: README.md 5 | summary: SUMMARY.md -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://github.com/riskers/blog/issues/24 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | _book -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | cache: npm 5 | 6 | notifications: 7 | email: 8 | recipients: 9 | - gaoyibobobo@gmail.com # 设置通知邮件 10 | on_success: change 11 | on_failure: always 12 | 13 | install: 14 | - npm install -g gitbook-cli 15 | - gitbook install 16 | 17 | script: 18 | - gitbook build 19 | 20 | after_script: 21 | - cd _book 22 | - git init 23 | - git remote add origin https://${REF} 24 | - git add . 25 | - git commit -m "Updated By Travis-CI With Build $TRAVIS_BUILD_NUMBER For Github Pages" 26 | - git push --force --quiet "https://${TOKEN}@${REF}" master:gh-pages 27 | 28 | branches: 29 | only: 30 | - master 31 | 32 | env: 33 | global: 34 | - REF=github.com/riskers/rxjs-note.git # 设置 github 地址 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rxjs note 2 | 3 | 目前 rxjs 最新版本是 v7,v4 之前的仓库是 https://github.com/Reactive-Extensions/RxJS , v5 迁移到了 https://github.com/ReactiveX/rxjs 。 4 | 5 | ## rxjs 的优势 6 | 7 | - Observable 标准化: https://github.com/tc39/proposal-observable 8 | - 多语言的支持: http://reactivex.io/ 中可以看到支持的众多语言,js 只是其中一种 9 | 10 | ## 学习笔记 11 | 12 | rxjs 的重点是 `observable` 和 `observer`,核心知识有 `operators`、`subject` 和 `schedule`。这里是我在学习 rxjs 时记录的概念和有趣的 demo。 13 | 14 | > 本书发布在 https://riskers.github.io/rxjs-note/ ,感谢 github pages / gitbook / travis 提供的便利。 15 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | - [关于](./README.md) 2 | 3 | ### 概念 4 | 5 | - [observable](./concepts/observable.md) 6 | - [observer](./concepts/observer.md) 7 | - [subject](./concepts/subject.md) 8 | - [operators](./concepts/operators.md) 9 | - [schedulers](./concepts/schedulers.md) 10 | 11 | ### 操作符 12 | 13 | - [概览](./operators/README.md) 14 | 15 | ### 与框架的结合 16 | 17 | - [React](./frameworks/rxjs-with-React/README.md) 18 | - [Angular](./frameworks/rxjs-with-Angular/README.md) 19 | 20 | ### 调试 21 | 22 | - [工具](./debugger/tools.md) 23 | - [测试](./debugger/test.md) 24 | 25 | ### demos 26 | 27 | - [box-drag-and-move](./demos/box-drag-and-move.md) 28 | - [race-request](./demos/race.md) 29 | - [eggs](./demos/eggs.md) -------------------------------------------------------------------------------- /alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riskers/rxjs-note/3ddb866ec80d53b47e1780f0c7e1255c4076ce6e/alipay.jpg -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "rxjs6+ 笔记", 3 | "author": "riskers", 4 | "plugins": [ 5 | "-lunr", 6 | "-search", 7 | "search-pro", 8 | "-sharing", 9 | "disqus", 10 | "github-buttons", 11 | "editlink", 12 | "ga", 13 | "-highlight", 14 | "prism", 15 | "bibtex-indexed-cite", 16 | "anchor-navigation-ex", 17 | "page-footer-ex", 18 | "splitter", 19 | "todo", 20 | "copy-code-button", 21 | "expandable-chapters-small", 22 | "donate" 23 | ], 24 | "pluginsConfig": { 25 | "search-pro": { 26 | "cutWordLib": "nodejieba", 27 | "defineWord": ["Gitbook Use"] 28 | }, 29 | "anchor-navigation-ex": { 30 | "showLevel": false 31 | }, 32 | "disqus": { 33 | "shortName": "rxjs-notebook" 34 | }, 35 | "github-buttons": { 36 | "buttons": [{ 37 | "user": "riskers", 38 | "repo": "rxjs-note", 39 | "type": "star", 40 | "size": "small", 41 | "count": true 42 | }] 43 | }, 44 | "editlink": { 45 | "base": "https://github.com/riskers/rxjs-note/blob/master", 46 | "label": "Edit", 47 | "multilingual": false 48 | }, 49 | "ga": { 50 | "token": "UA-131910384-1" 51 | }, 52 | "prism": { 53 | "ignore": [ 54 | "mermaid", 55 | "eval-js" 56 | ], 57 | "css": [ 58 | "prismjs/themes/prism-solarizedlight.css" 59 | ] 60 | }, 61 | "page-footer-ex": { 62 | "copyright": "By [riskers](https://github.com/riskers),使用[知识共享 署名-相同方式共享 4.0协议](https://creativecommons.org/licenses/by-sa/4.0/)发布", 63 | "markdown": true, 64 | "update_label": "此页面修订于:", 65 | "update_format": "YYYY-MM-DD HH:mm:ss" 66 | }, 67 | "bibtex-indexed-cite": { 68 | "path": "/" 69 | }, 70 | "donate": { 71 | "wechat": "https://riskers.github.io/rxjs-note/wechat.jpg", 72 | "alipay": "https://riskers.github.io/rxjs-note/alipay.jpg", 73 | "title": "", 74 | "button": "赏", 75 | "alipayText": "支付宝打赏", 76 | "wechatText": "微信打赏" 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /concepts/observable.md: -------------------------------------------------------------------------------- 1 | ## Observable 2 | 3 | `Observable` 是一个可以推送多个值的惰性集合。 4 | 5 | | | 单个值 | 多个值 | 6 | | :---- | :---- | :---- | 7 | | 拉取 | Function | Iterator | 8 | | 推送 | Promise | Observable | 9 | 10 | * 拉取 VS 推送 11 | * 所谓『拉取』,就是**生产者被请求时产生数据,消费者决定何时请求数据**: 在拉取体系中,由消费者来决定何时从生产者那里接收数据,生产者本身不知道数据是何时交付到消费者手中的 12 | * 所谓『推送』,就是**生产者按自己的节奏产生数据,消费者对收到的数据做出反应**: 在推送体系中,由生产者来决定何时把数据发送给消费者。消费者本身不知道何时会接收到数据 13 | * 单值 VS 多值 14 | * 所谓单值,就是**只能返回一个值** 15 | * 所谓多值,就是**可以随着时间的推移“返回”多个值** 16 | 17 | ### Function 18 | 19 | > 单值 + 拉取 20 | 21 | ```js 22 | /* 23 | * 函数声明就是生产者,产生数据 24 | * 函数调用就是消费者,消费数据 25 | */ 26 | 27 | function foo() { 28 | return 42; 29 | } 30 | 31 | foo() // 只能得到一个值,永远不会是第二个值 32 | ``` 33 | 34 | ### Iterator 35 | 36 | > 多值 + 拉取 37 | 38 | ```js 39 | /* 40 | * iter 就是生产者 41 | * next() 是消费者,决定了何时消费 42 | */ 43 | 44 | let arr = ['a', 'b', 'c']; 45 | let iter = arr[Symbol.iterator](); 46 | 47 | // 每一次 next() 调用得到的值都不一样,所以是多值 48 | iter.next() // { value: 'a', done: false } 49 | iter.next() // { value: 'b', done: false } 50 | iter.next() // { value: 'c', done: false } 51 | iter.next() // { value: undefined, done: true } 52 | ``` 53 | 54 | ### Promise 55 | 56 | > 单值 + 推送 57 | 58 | ```js 59 | // 完全由 Promise 对象控制何时发送数据: then() 60 | new Promise((resolve, reject) => { 61 | // ... 62 | }) 63 | .then(data => { 64 | // ... // 只能产生一个值 65 | }) 66 | ``` 67 | 68 | ### Observable 69 | 70 | > 多值 + 推送 71 | 72 | 对于 Observable 来说这个反应是 `subscribe` 73 | 74 | ```js 75 | // 完全由 Observable 对象控制何时发送数据: subscribe 76 | const observable$ = Observable.create(observer => { 77 | 78 | // 每一次 next() 输出的值都不一样,所以是多值 79 | observer.next('hi') 80 | observer.next('riskers') 81 | observer.next('hello world') 82 | 83 | setTimeout(() => { 84 | observer.next('ee') 85 | }, 2000) 86 | 87 | observer.next('last') 88 | }) 89 | 90 | observable$.subscribe({ 91 | next: (value) => console.log(value), 92 | error: err => console.log(err), 93 | complete: () => console.log('complete'), 94 | }) 95 | ``` 96 | -------------------------------------------------------------------------------- /concepts/observer.md: -------------------------------------------------------------------------------- 1 | ## Observer 2 | 3 | `Observer` 是 Observable 发送的值的消费者。 4 | 5 | ```js 6 | // 观察者只是有三个回调函数的对象,每个回调函数对应一种 Observable 发送的通知类型: 7 | const observer = { 8 | next: (value) => console.log(value), 9 | error: err => console.log(err), 10 | complete: () => console.log('complete'), 11 | } 12 | 13 | // 要使用观察者,需要把它提供给 Observable 的 subscribe 方法: 14 | observable$.subscribe(observer) 15 | ``` 16 | 17 | subscribe 也可以是一个严格按照顺序的参数: 18 | 19 | ```js 20 | observable$.subscribe( 21 | (val) => {console.log(val)}, 22 | err => console.log(err), 23 | () => console.log('complete'), 24 | ) 25 | ``` -------------------------------------------------------------------------------- /concepts/operators.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /concepts/schedulers.md: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | https://stackblitz.com/edit/rxjs-6kbfxh -------------------------------------------------------------------------------- /concepts/subject.md: -------------------------------------------------------------------------------- 1 | ## Subject 2 | 3 | 首先确定一个概念,**Subject 即是 Observable 也是 Observer**: 4 | 5 | [stackblitz](https://stackblitz.com/edit/rxjs-tm5sj9) 6 | 7 | > subject 在收到 $source 新消息时,会通知内部所有观察者(observerA 、observerB) 8 | 9 | 何时使用 Subject: 10 | 11 | * 需要共享相同的 observable 执行。 12 | * 当需要决定观察者迟来时该怎么做,是否使用 ReplaySubject、BehaviorSubject? 13 | * 需要完全控制 next()、error() 和 completed() 方法。 14 | 15 | ## Cold Observable 的问题 16 | 17 | cold ovservable 是无法多播的,因为数据不同步: [stackblitz](https://stackblitz.com/edit/rxjs-rjua2a) 18 | 19 | ## 多播 20 | 21 | 而将 cold -> hot 后,就可以多播了: [stackblitz](https://stackblitz.com/edit/rxjs-g62yrj) 22 | 23 | [Subject 实现的多播](https://stackblitz.com/edit/rxjs-jzb8d8) 24 | 25 | ## subject 不能重复使用 26 | 27 | [stackblitz](https://stackblitz.com/edit/rxjs-3gkwka) 28 | 29 | 很多人會直接把這個特性拿來用在不知道如何建立 Observable 的狀況: 30 | 31 | ```js 32 | class MyButton extends React.Component { 33 | constructor(props) { 34 | super(props); 35 | this.state = { count: 0 }; 36 | this.subject = new Rx.Subject(); 37 | 38 | this.subject 39 | .mapTo(1) 40 | .scan((origin, next) => origin + next) 41 | .subscribe(x => { 42 | this.setState({ count: x }) 43 | }) 44 | } 45 | render() { 46 | return 47 | } 48 | } 49 | ``` 50 | 51 | 因为 React API 的关系,如果我們想要把 React Event 转换成 observable 就可以用 Subject 幫我們做到這件事;但绝大多数的情況我們是可以透過 `Observable.create` 來做到這件事,像下面这样: 52 | 53 | ```js 54 | import { Observable } from 'rxjs' 55 | 56 | const example = Observable.create(observer => { 57 | const source = getSomeSource(); // 某個数据源 58 | source.addListener('some', (some) => { 59 | observer.next(some) 60 | }) 61 | }); 62 | ``` 63 | 64 | 大概就會像上面這樣,如果沒有合適的 creation operators 我們還是可以利用 Observable.create 來建立 observable,除非真的因為框架限制才會直接用 Subject。 65 | 66 | ## BehaviorSubject 67 | 68 | 很多時候希望 Subject 能代表當下的状态,也就是說如果今天有一個新的 subscribe,我們希望 Subject 能立即給出最新的值,而不是沒有值: 69 | 70 | [stackblitz](https://stackblitz.com/edit/rxjs-okmhrl) 71 | 72 | ## ReplaySubject 73 | 74 | 希望 Subject 代表事件,但又能在新 subscribe 时重新发送最后的几個元素 75 | 76 | [stackblitz](https://stackblitz.com/edit/rxjs-jwa7n9) 77 | 78 | > 可能會有人以為 ReplaySubject(1) 是不是就等同於 BehaviorSubject,其實是不一樣的,BehaviorSubject 在建立時就會有起始值,比如 BehaviorSubject(0) 起始值就是 0,BehaviorSubject 是代表著狀態而 ReplaySubject 只是事件的重放而已 79 | 80 | ## AsyncSubject 81 | 82 | AsyncSubject 会在 subject 结束后送出最后一個值,其实这个行为跟 Promise 很像,都是等到事情结束后送出一個值,但实际上我們非常非常少用到 AsyncSubject,絕大部分的時候都是使用 BehaviorSubject 跟 ReplaySubject 或 Subject。 83 | 84 | [stackblitz](https://stackblitz.com/edit/rxjs-hweoev) 85 | 86 | 87 | ## 多播操作符 88 | 89 | ### multicast 90 | 91 | refCount: 建立自动 connect 的 Observable 92 | 93 | [stackblitz](https://stackblitz.com/edit/rxjs-afv7vh) 94 | 95 | [multicast operator 实现的多播](https://stackblitz.com/edit/rxjs-3aaggb) 96 | 97 | ### publish 98 | 99 | `multicast` 的简写: 100 | 101 | ```js 102 | var source = interval(1000).pipe( 103 | publish(), 104 | refCount() 105 | ) 106 | 107 | // 等同于: 108 | /* var source = interval(1000).pipe( 109 | multicast(new Rx.Subject()), 110 | refCount() 111 | ) */ 112 | ``` 113 | 114 | 变体: 115 | 116 | * publishBehavior 117 | 118 | ```js 119 | var source = interval(1000).pipe( 120 | publishBehavior(0), 121 | refCount() 122 | ) 123 | 124 | // 等同于: 125 | /* var source = interval(1000).pipe( 126 | multicast(new Rx.BehaviorSubject(0)), 127 | refCount() 128 | ) */ 129 | ``` 130 | 131 | * publishReplay 132 | 133 | ```js 134 | var source = interval(1000).pipe( 135 | publishReplay(1), 136 | refCount() 137 | ) 138 | 139 | // 等同于: 140 | /* var source = interval(1000).pipe( 141 | multicast(new Rx.ReplaySubject(1)), 142 | refCount() 143 | ) */ 144 | ``` 145 | 146 | * publishLast 147 | 148 | ```js 149 | var source = interval(1000).pipe( 150 | publishLast(), 151 | refCount() 152 | ) 153 | 154 | // 等同于: 155 | /* var source = interval(1000).pipe( 156 | multicast(new Rx.AsyncSubject(1)), 157 | refCount() 158 | ) */ 159 | ``` 160 | 161 | ### share 162 | 163 | `publish` + `refCount` 的简写: 164 | 165 | ```js 166 | var source - interval(1000).pipe( 167 | share(), 168 | ) 169 | 170 | /* var source = interval(1000).pipe( 171 | publish(), 172 | refCount() 173 | ) */ 174 | 175 | /* var source = interval(1000).pipe( 176 | multicast(new Rx.Subject()), 177 | refCount() 178 | ) */ 179 | ``` 180 | 181 | [share operator 实现的多播](https://stackblitz.com/edit/rxjs-3nhqpt) -------------------------------------------------------------------------------- /debugger/test.md: -------------------------------------------------------------------------------- 1 | # 使用测试弹珠来学习 rxjs -------------------------------------------------------------------------------- /debugger/tools.md: -------------------------------------------------------------------------------- 1 | * [rxMarbles](http://rxmarbles.com/) 2 | * [rxfiddle](http://rxfiddle.net/#code=&type=editor) 3 | * https://jaredforsyth.com/rxvision/ 4 | * https://rxviz.com/ 5 | * [reactive.how](https://reactive.how/) 6 | 7 | ## marbles 图怎么画 8 | 9 | ![](https://github-riskers-blog.oss-cn-qingdao.aliyuncs.com/20181214172331.png) 10 | 11 | > ---- https://zhuanlan.zhihu.com/p/25115712 12 | -------------------------------------------------------------------------------- /demos/box-drag-and-move.md: -------------------------------------------------------------------------------- 1 | [stackblitz](https://stackblitz.com/edit/rxjs-mouse-move?embed=1&file=index.ts) 2 | 3 | 这是一个复杂的 stream,用 marbles 表示: 4 | 5 | ```bash 6 | move$: -(m)--(m)--(m)--(m)--(m)--(m)---- 7 | ----------------- 8 | | takeUntil | 9 | ----------------- 10 | up$: ---------------------------(u)--- 11 | | 12 | endMove$: -(m)--(m)--(m)--(m)--(m)--| 13 | ``` 14 | 15 | ```bash 16 | down$: -(d)------------------------- 17 | map 18 | endMove$: -(m)--(m)--(m)--(m)--(m)--| 19 | | 20 | -(o)------------------------- 21 | \ 22 | (mouseEvent) 23 | concatAll 24 | -(mouseEvent)---------------- 25 | map 26 | -(x, y)---------------------- 27 | ``` 28 | -------------------------------------------------------------------------------- /demos/clickclickclick.md: -------------------------------------------------------------------------------- 1 | https://stackblitz.com/edit/rxjs-e65jbu -------------------------------------------------------------------------------- /demos/eggs.md: -------------------------------------------------------------------------------- 1 | # 彩蛋 2 | 3 | [Code](https://stackblitz.com/edit/rxjs-jwswfn) 4 | 5 | 运用: 6 | 7 | * bufferWhen 8 | * map -------------------------------------------------------------------------------- /demos/medias/race_promise_jsonp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riskers/rxjs-note/3ddb866ec80d53b47e1780f0c7e1255c4076ce6e/demos/medias/race_promise_jsonp.png -------------------------------------------------------------------------------- /demos/race.md: -------------------------------------------------------------------------------- 1 | ## 竞态请求 2 | 3 | [stackblitz](https://stackblitz.com/edit/rxjs-race-jsonp?embed=1&file=index.ts) 4 | 5 | 在前端中经常处理请求,现在有需求: 每 10ms 请求一次百度搜索关键词,请求十个,然后显示出来。 6 | 7 | 在百度接口响应速度响应时间小于 10ms 的时候这样是没有问题的,但是这个接口响应时间在 80ms 左右,那么就会出现竞态。因为请求是异步的,只能保证发送请求的顺序是固定的,但是不能保证返回响应的顺序是一定的。可能出现**先发送的请求后响应的情况** 8 | 9 | 比如 10 | 11 | 12 | 13 | 但是 rxjs jsonp 就不会,因为 `switchMap` 会把在进行中的网络请求取消,在 10ms 后发现没有响应回来,就会取消前一个请求的订阅发下一个请求。 14 | 15 | > 注意,是取消前一个请求的订阅,不是取消前一个请求,请求是一定会发出去的。 16 | -------------------------------------------------------------------------------- /demos/search-github-users.md: -------------------------------------------------------------------------------- 1 | [stackblitz](https://stackblitz.com/edit/search-github-users?embed=1&file=index.ts) -------------------------------------------------------------------------------- /demos/thrice-click.md: -------------------------------------------------------------------------------- 1 | 2 | ```js 3 | import { fromEvent } from 'rxjs'; 4 | import { map, buffer, debounceTime } from 'rxjs/operators'; 5 | 6 | const mouse$ = fromEvent(document, 'click') 7 | 8 | const buff$ = mouse$.pipe( 9 | debounceTime(250), 10 | ) 11 | 12 | const click$ = mouse$.pipe( 13 | buffer(buff$), 14 | map(list => { 15 | return list.length; 16 | }), 17 | filter(x => x === 2), // 这里不判断就可以判断点击次数 18 | ) 19 | 20 | click$.subscribe(() => { 21 | console.log('doubleclick') 22 | }) 23 | ``` -------------------------------------------------------------------------------- /frameworks/rxjs-with-Angular/README.md: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | -------------------------------------------------------------------------------- /frameworks/rxjs-with-React/README.md: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | -------------------------------------------------------------------------------- /frameworks/rxjs-with-React/assets/redux.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riskers/rxjs-note/3ddb866ec80d53b47e1780f0c7e1255c4076ce6e/frameworks/rxjs-with-React/assets/redux.jpg -------------------------------------------------------------------------------- /frameworks/rxjs-with-React/assets/rxjs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riskers/rxjs-note/3ddb866ec80d53b47e1780f0c7e1255c4076ce6e/frameworks/rxjs-with-React/assets/rxjs.jpg -------------------------------------------------------------------------------- /literature.bib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riskers/rxjs-note/3ddb866ec80d53b47e1780f0c7e1255c4076ce6e/literature.bib -------------------------------------------------------------------------------- /main.md: -------------------------------------------------------------------------------- 1 | # rxjs 的优势 2 | 3 | * Observable 标准化 4 | * 多语言的支持 5 | 6 | https://github.com/tc39/proposal-observable 7 | 8 | # rxjs 9 | 10 | * 初识 rxjs: https://stackblitz.com/edit/rxjs6-study-beginners 11 | * cold Obvservable / hot Observable: 绝大多数的创建类操作符创建的都是 Cold Obvserable 对象(除了 ajax / fromEvent ...): 12 | * cold: 录像 13 | * hot: 直播 14 | * Subject: https://stackblitz.com/edit/rxjs6-study-subject 15 | * 既是 Observable,也是 observer。主要解决多播问题。 16 | 17 | 18 | ---- 19 | 20 | # Observable 21 | 22 | 第一步,先了解 [Observer Pattern](http://www.jianshu.com/p/8051934077ef) 和 [Iterator Pattern](http://www.jianshu.com/p/f054f68508e1),然后可以发现他们有个共通的特性,就是他们都是渐进式的 (progressive) 取数据,差别在于 23 | 24 | * Observer 是生产者,push 数据 25 | * Iterator 是消费者,pull 数据 26 | 27 | ![](http://7xlc2a.com1.z0.glb.clouddn.com/15064082339931.png) 28 | 29 | 第二步,Observable 其实就是这两个设计模式的结合实现,Observable 具有生产者 push 数据的特性,同时也像序列,拥有序列处理数据的方法(map、filter等) 30 | 31 | ```js 32 | // Iterator Pattern 33 | var observable = Rx.Observable.create(observer => { 34 | observer.next('Jerry') 35 | observer.next('Anna') 36 | }) 37 | 38 | // Observer Pattern 39 | observable.subscribe({ 40 | next: x => console.log(x), 41 | error: err => console.log(err), 42 | complete: () => console.log('complete') 43 | }) 44 | ``` 45 | 46 | > 注意 Observable 的 Observer Pattern 实现与一般的 DOM 事件的 Observer Pattern 是不同的。 47 | > DOM 事件的订阅会有一份清单 48 | > Observable 的 Observer Pattern 实现就像是执行了一个 function 49 | 50 | 51 | # rxjs 52 | 53 | * 一个核心(Observable + Operators) 54 | * 三个重点 55 | * Observer 一定会用到且最简单的 56 | * Subject 使用频率低很多,很多 RxJS 相关的 Library 或 Framework 用到,如 redux-observable 57 | * Schedulers 58 | 59 | # Observable 60 | 61 | ## 创建 Observable 62 | 63 | 相当于创建数据源 64 | 65 | * 方法一 `Rx.Observable.create` 66 | * 方法二 creation operator `from`、`of`、`fromEvent`、`fromPromise` ... 67 | 68 | > 虽然 Observable 可以被 create,但实际上我們通常都使用 creation operator 像是 from, of, fromEvent, fromPromise 等。 69 | 70 | **** 71 | 72 | **Observable 可以同时处理同步与异步行为** 73 | 74 | ```js 75 | var observable = Rx.Observable 76 | .create(function(observer) { 77 | observer.next('Jerry'); // RxJS 4.x 以前的版本用 onNext 78 | observer.next('Anna'); 79 | 80 | setTimeout(() => { 81 | observer.next('RxJS 30 days!'); 82 | }, 30) 83 | }) 84 | 85 | console.log('start'); 86 | observable.subscribe(function(value) { 87 | console.log(value); 88 | }); 89 | console.log('end'); 90 | ``` 91 | 92 | ```js 93 | start 94 | Jerry 95 | Anna 96 | end 97 | RxJS 30 days! 98 | ``` 99 | 100 | ***** 101 | 102 | **深入 Observable(与 Array 的区别)** 103 | 104 | 1. 延迟计算:Observable 一样要等到 subscribe 后才开始对元素运算 105 | 106 | ```js 107 | // 没有 subscribe ,下面代码不会运算 108 | var source = Rx.Observable.from([1,2,3,4,5]); 109 | var example = source.map(x => x + 1); 110 | 111 | // 而数组就直接运算了 112 | var source = [1,2,3,4,5]; 113 | var example = source.map(x => x + 1); 114 | ``` 115 | 116 | 2. 渐进式取值:一个元素运算到底,而不是像数组运算完全部元素再返回 117 | 118 | ![](http://7xlc2a.com1.z0.glb.clouddn.com/15064984543104.gif) 119 | 120 | ![](http://7xlc2a.com1.z0.glb.clouddn.com/15064984665738.gif) 121 | 122 | 123 | 124 | 125 | ## Observer 126 | 127 | Observable 可以被订阅,订阅 Observable 的对象就是 Observer。Observer 有三个方法,每当 Observable 发生事件时,便会执行对应的 Observer 方法: 128 | 129 | * next:每当 Observable 发出新值时执行 130 | * complete 131 | * error 132 | 133 | ```js 134 | var observable = Rx.Observable.create(observer => { 135 | observer.next('Jerry') 136 | observer.error('xx') 137 | observer.next('Anna') 138 | }) 139 | 140 | observable.subscribe({ 141 | next: x => console.log(x), 142 | error: err => console.log(err), 143 | complete: () => console.log('complete') 144 | }) 145 | ``` 146 | 147 | **** 148 | 149 | **取消订阅 unsubscribe()** 150 | 151 | ```js 152 | let source$ = observable.subscribe(...) 153 | 154 | source$.unsubscribe() 155 | ``` 156 | 157 | ## Operator 158 | 159 | 每个 operator 都会回传一个新的 observable 160 | 161 | **** 162 | 163 | **Marble diagrams** 164 | 165 | * `-`: 一串 `-` 代表一个 observable 166 | * `|`: 代表 observable 结束 167 | * `X`: 代表有错误发生 168 | * `()`: 同步 169 | 170 | ```js 171 | // creation operation 172 | /* 173 | * -----0-----1-----2-----3---... 174 | **/ 175 | Rx.Observable.interval(1000) 176 | 177 | /* 178 | * (1234)| 179 | **/ 180 | Rx.Observable.of(1,2,3,4) // 同步 181 | ``` 182 | 183 | ```js 184 | // filter operation 185 | /* 186 | source: -----0-----1-----2-----3--... 187 | map(x => x + 1) 188 | newest: -----1-----2-----3-----4--... 189 | 190 | source : -----0-----1-----2-----3--.. 191 | take(3) 192 | example: -----0-----1-----2| 193 | **/ 194 | ``` 195 | 196 | **** 197 | 198 | [RxJS operators](https://gist.github.com/riskers/c6c8fa7cf51b0d9cb39b443aaf0d48b4) 看这里 199 | 200 | // concatAll http://ithelp.ithome.com.tw/articles/10187333 201 | // combineLatest http://ithelp.ithome.com.tw/articles/10187638 202 | // scan buffer http://ithelp.ithome.com.tw/articles/10187882 203 | 204 | 205 | # Subject 206 | 207 | ## Subject 是什么? 208 | 209 | * Subject 是 Observable 又是 Observer 210 | 211 | ```js 212 | var source = Rx.Observable.interval(1000).take(10) 213 | var observerA = { 214 | next: x => console.log('A:', x), 215 | error: err => console.log(err), 216 | complete: () => console.log('complete') 217 | } 218 | 219 | var observerB = { 220 | next: x => console.log('B', x), 221 | error: err => console.log(err), 222 | complete: () => console.log('complete') 223 | } 224 | 225 | var subject = new Rx.Subject() 226 | 227 | source.subscribe(subject) // subject 作为 observer 228 | 229 | subject.subscribe(observerA) // subject 作为 observable 230 | 231 | setTimeout(() => { 232 | subject.subscribe(observerB) // subject 作为 observable 233 | }, 1000) 234 | ``` 235 | 236 | * Subject 会对内部的 observers 清单进行组播 multicast(第二次订阅 source 不會從头开始接收元素,而是从第一次订阅到当前处理的元素开始发送) 237 | 238 | ## BehaviorSubject / ReplaySubject / AsyncSubject 239 | 240 | * BehaviorSubject 用来呈现当前的值,会记住最新一次发送的元素,并把该元素当做目前的值 241 | * ReplaySubject 能在新订阅时重新发送最后的几个元素 242 | * AsyncSubject 在 subject 结束后送出最后一个值 243 | 244 | ## multicast / refCount / publish / share 245 | 246 | 略 247 | 248 | ## Subject 的用处 249 | 250 | 用在不知道如何建立 Observable 的状况,比如 React 251 | 252 | ![](http://7xlc2a.com1.z0.glb.clouddn.com/30_天精通_RxJS_25_:Subject_總結_-_iT_邦幫忙__一起幫忙解決難題,拯救_IT_人的一天.png) 253 | 254 | 因为 React API 的关系,只能通过 Subject 帮助我们把 React Event 转化为 Observable。但绝大多数的情況我們是可以通過 Observable.create 來做到這件事,像下面這樣 255 | 256 | ![](http://7xlc2a.com1.z0.glb.clouddn.com/30_天精通_RxJS_25_:Subject_總結_-_iT_邦幫忙__一起幫忙解決難題,拯救_IT_人的一天-1.png) 257 | 258 | 259 | ## Subject 与 Observable 区别 260 | 261 | 永远记得 Subject 其实是 Observer Design Pattern 的实作,所以当 observer 订阅到 subject 时,subject 会把订阅者塞到一份订阅者清单,在元素发送时就是在遍历这份清单,并把元素一一送出,这跟 Observable 像是一个 function 执行是完全不同的(请参考 05 篇)。 262 | 263 | Subject 之所以具有 Observable 的所有方法,是因为 Subject 继承了 Observable 的型别,其实 Subject 型别中主要实做的方法只有 next、error、 complete、subscribe 及 unsubscribe 这五个方法,而这五个方法就是依照 Observer Pattern 下去实作的。 264 | 265 | 总而言之,Subject 是 Observable 的子类别,这个子类别当中用上述的五个方法实作了 Observer Pattern,所以他同时具有 Observable 与 Observer 的特性,而跟 Observable 最大的差异就是 Subject 是具有状态的,也就是储存的那份清单! 266 | 267 | 268 | # Scheduler 269 | 270 | RxJS 可以同时处理同步和异步,所以我们经常搞不清楚 `from`、`range` 这些是同步还是异步送出数据。`Scheduler` 就是解决这个问题的。 271 | 272 | ## 什么是 Scheduler 273 | 274 | Scheduler 控制一个 subscription 的订阅何时开始和何时发送 275 | 276 | 1. Scheduler 是一个数据结构 277 | 2. Scheduler 是执行上下文 278 | 3. Scheduler 有一个(虚拟的)时钟 279 | 280 | ------ 281 | 282 | RxJS 是通过使用可观察序列来编写异步和基于事件的库 283 | 284 | > 把 RxJS 当作事件的 lodash 285 | 286 | 响应式编程继承自函数式编程: 287 | 288 | > RxJS是结合了观察者模式(Observer),迭代器模式(Iterator)和函数式编程结合,以满足管理事件序列的理想方式的需要 289 | 290 | * 核心类型 Observable:表示未来值或事件的可调用集合的思想 291 | * 周边类型 Observer Scheduler Subject 292 | * Observer:回调函数的集合,这些回调函数知道如何监听 Observable 传递的值 293 | * Subscribe:表示 Observable 的执行,主要用于取消回调 294 | * Subject:等同于 EventEmitter,向多个 Observer 广播数值或者事件的唯一方法 295 | * Schedule:控制并发的集中式调试程序,允许我们当运算在 setTimeout 等发生时进行协调 296 | * 操作符 Operators:纯函数,用于实现函数式编程风格,使用 map、filter、concat 等来处理集合 297 | 298 | ## Observable 299 | 300 | 推送多个数值集合的惰性计算 301 | 302 | 303 | | | 单值 | 多值 | 304 | | --- | --- | --- | 305 | | 拉 | 函数 | 迭代器 | 306 | | 推 | Promise | Observable | 307 | 308 | 309 | ### 创建 Observable 310 | 311 | * 方法一 Rx.Obsevable.create 312 | 313 | ```js 314 | let source$ = Rx.Observable.create(observer => { 315 | observer.next(1) 316 | observer.next(2) 317 | observer.next(3) 318 | 319 | setTimeout(()=>{ 320 | observer.next(4) 321 | observer.complete() 322 | }, 1000) 323 | }) 324 | 325 | // subscribe: 提供回调处理传递的数据 326 | // 仅当一个 Observable 被订阅时才运行的惰性计算 327 | source$.subscribe({ 328 | next: x => {console.log(x)} 329 | }) 330 | ``` 331 | 332 | * 方法二 创建操作符(静态方法) from、of、interval、fromEvent 333 | 334 | ```js 335 | Rx.Observable.of(1,2,3).subscribe(console.log) 336 | 337 | Rx.Observable.from([1,2,3]).subscribe(console.log) 338 | ``` 339 | 340 | ### 注销 341 | 342 | ```js 343 | $source.unsubscribe() 344 | ``` 345 | 346 | ## Observer 347 | 348 | Observable 推送的数值的消费者,包含一系列回调函数 349 | 350 | ```js 351 | var observer = { 352 | next: x => {}, 353 | complete: () => {}, 354 | error: () => {} 355 | } 356 | 357 | source$.subscribe(observer) 358 | ``` 359 | 360 | ## Subscription 361 | 362 | 表示 Observable 的执行的一次性资源 363 | 364 | ```js 365 | let subscription = source$.subscribe(observer) 366 | 367 | // leter 368 | subscription.unsubscribe() 369 | ``` 370 | 371 | ```js 372 | var source1$ = Rx.Observable.interval(400) 373 | var source2$ = Rx.Observable.interval(300) 374 | 375 | var subscription = source1$.subscribe( x => console.log(x)) 376 | var childSubscription = source2$.subscribe( x => console.log(x) ) 377 | 378 | subscription.add(childSubscription) 379 | 380 | /* 381 | setTimeout(()=>{ 382 | subscription.remove(childSubscription) 383 | }, 1000) 384 | */ 385 | 386 | setTimeout(()=>{ 387 | subscription.unsubscribe() 388 | }, 1200) 389 | ``` 390 | 391 | ## Subject 392 | 393 | 一种特殊的 Observable,允许将数值组播给多个 Observer 394 | 395 | Subject 是 Observable ,也是 Observer 396 | 397 | ```js 398 | var subject = new Rx.Subject() 399 | 400 | subject.subscribe({ 401 | next: v => console.log('A', v) 402 | }) 403 | 404 | subject.subscribe({ 405 | next: v => console.log('B', v) 406 | }) 407 | 408 | subject.next(1) 409 | subject.next(2) 410 | ``` 411 | 412 | ```js 413 | var subject = new Rx.Subject() 414 | subject.subscribe({ 415 | next: v => console.log('A', v) 416 | }) 417 | subject.subscribe({ 418 | next: v => console.log('B', v) 419 | }) 420 | 421 | var observable = Rx.Observable.from([1,2,3]) 422 | observable.subscribe(subject) 423 | 424 | // 等同于, subject 是组播 425 | var a$ = Rx.Observable.from([1,2,3]) 426 | 427 | a$.subscribe(v => console.log('A', v)) 428 | a$.subscribe(v => console.log('B', v)) 429 | ``` 430 | 431 | * 组播 Observable,使用 `multicast` operator 生成一个拥有 conncet 方法 的 ConnectableObservable 432 | * BehaviorSubject - replays one, only before completion 433 | * ReplaySubject - replays many, before or after completion 434 | * AsyncSubject - replays one, only if completed 435 | 436 | 437 | ## Operator 438 | 439 | [rxmarbles](http://rxmarbles.com/) 440 | 441 | [rxfiddle](http://rxfiddle.net/) 442 | 443 | 返回一个新的 Observable 的 Observable 方法 444 | 445 | * 静态方法 446 | * create 447 | * empty 448 | * of 449 | * from 450 | * fromEvent 451 | * interval 452 | * timer 453 | * 实例方法 454 | 455 | ## Scheduler 456 | 457 | 控制 Observable 何时执行,通知何时发送 458 | 459 | 460 | -------------------------------------------------------------------------------- /operators/README.md: -------------------------------------------------------------------------------- 1 | # 操作符 2 | 3 | [learn rxjs operators](https://github.com/RxJS-CN/learn-rxjs-operators) 已经很详细了,这里补充一些有意思的资料和总结。 4 | 5 | * scan: 顧名思義【掃描】就是將傳入 RxJS 運算子的事件資料,一筆一筆處理過一遍,每次處理的過程都會累加,並且處理一筆就 emit 一筆新的事件資料出來。 6 | * map: 顧名思義【對應】就是將一筆傳入的資料,對應到另一種格式的資料,通常用來轉換資料之用。 7 | *concat: 顧名思義【串接】就是將一筆一筆的資料串接在一起,通常是把多個 Observable 物件串接成一個新的 Observable 物件。 8 | * switch: 顧名思義【交換】就是將一筆資料 "交換成" 另一種資料,當連續交換的事件發生時,還沒交換的資料就會被放棄。 9 | * merge: 顧名思義【合併】就是將多筆資料合併成一筆,通常是將多個 Observable 物件合併成一個 Observable 物件。 10 | * flat: 其用途跟 merge 完全一樣,在語意上,通常代表把多個 Observable 物件「壓平」成一個 Observable 物件。 11 | * exhaust: 顧名思義【耗盡】就是要把原本 Observable 物件送出的資料都跑完,才能繼續跑下一個。 12 | * zip: 這個單字有【拉鍊】的意思,你要發揮一點想像力,假設衣服的兩端,拉鍊拉起來之後,每一節都會平整的被湊在一起。 13 | * combine: 顧名思義【組合】就是將多筆資料組合在一起。 14 | * forkJoin: 這裡的 fork 是【走進岔路】的意思,Join 則是【從不同的岔路合併回來】。通常意思代表多個 Observable 物件同時執行,但全部執行完之後,才會 emit 所有資料,等大家從岔路走回來的感覺。 15 | 16 | ----- 17 | 18 | * https://zhuanlan.zhihu.com/p/39359316: 里面的图非常有意思,多观察观察。 19 | 20 | ![](https://github-riskers-blog.oss-cn-qingdao.aliyuncs.com/20181214220218.png) 21 | 22 | * xxxAll: 顧名思義【全部】就是將所有傳入的 Observabe 物件,全部一起處理。 23 | * xxxLatest: 顧名思義【最新的】就是取得最新資料的意思。 24 | * xxxTo: 這裡的 To 有【表示結果】的意思,也就是直接把特定結果 emit 出去。 25 | * xxxMap: 這裡的 Map 有【對應】的意思,但是在 RxJS 的領域中,通常代表著在 Operator 中會對應到另一個不同的 Observable 物件。 26 | * xxxMapTo: 這裡就是 Map + To 的意思,但是在 RxJS 的領域中,通常代表著在 Operator 中會對應到另一個自行指定的 Observable 物件。 27 | * xxxScan: 顧名思義【掃描】就是將傳入 RxJS 運算子的事件資料,一筆一筆處理過一遍,每次處理的過程都會累加,並且處理一筆就 emit 一筆新的事件資料出來。 28 | 29 | > ---- https://blog.miniasp.com/post/2018/09/06/Clarify-some-confused-RxJS-operators.aspx 30 | 31 | -------------------------------------------------------------------------------- /operators/combine operators/README.md: -------------------------------------------------------------------------------- 1 | * combineAll 2 | * combineLatest 3 | * concat 4 | * concatAll 5 | * exhaust 6 | * forkJoin 7 | * merge 8 | * mergeAll 9 | * race 10 | * startWith 11 | * switch 12 | * withLatestFrom 13 | * zip 14 | * zipAll -------------------------------------------------------------------------------- /operators/createion operators/README.md: -------------------------------------------------------------------------------- 1 | * ajax 2 | * bindCallback 3 | * bindNodeCallback 4 | * create 5 | * defer 6 | * empty 7 | * from 8 | * fromEvent 9 | * fromEventPattern 10 | * fromPromise 11 | * generate 12 | * interval 13 | * never 14 | * of 15 | * repeat 16 | * repeatWhen 17 | * range 18 | * throw 19 | * timer 20 | -------------------------------------------------------------------------------- /operators/createion operators/ajax.md: -------------------------------------------------------------------------------- 1 | [stackblitz](https://stackblitz.com/edit/rxjs-drkpef?embed=1&file=index.ts) -------------------------------------------------------------------------------- /operators/createion operators/from.md: -------------------------------------------------------------------------------- 1 | ```js 2 | from(['H', 'e', 'l', 'l', 'o']) 3 | .subscribe(e => { 4 | console.log(e) 5 | }) 6 | ``` -------------------------------------------------------------------------------- /operators/createion operators/of.md: -------------------------------------------------------------------------------- 1 | ```js 2 | import { of } from 'rxjs' 3 | 4 | of('H', 'e', 'l', 'l', 'o') 5 | .subscribe(e => { 6 | console.log(e) 7 | }) 8 | ``` -------------------------------------------------------------------------------- /operators/custom operator/README.md: -------------------------------------------------------------------------------- 1 | How? 2 | 3 | ```js 4 | // const m = (cb) => { 5 | // return Observable.create((observer) => { 6 | // return this.subscribe( 7 | // value => { 8 | // observer.next(cb(value)) 9 | // } 10 | // ) 11 | // }) 12 | // } 13 | 14 | // from(['H', 'e', 'l', 'l', 'o']) 15 | // .pipe( 16 | // map(item => item + '~~~~') 17 | // ) 18 | // .subscribe(e => { 19 | // console.log(e) 20 | // }) 21 | ``` 22 | 23 | 每個 operator 都會回傳一個新的 observable,而我們可以透過 create 的方法建立各種 operator。 24 | 25 | -------------------------------------------------------------------------------- /operators/filters operators/README.md: -------------------------------------------------------------------------------- 1 | * debounce 2 | * debounceTime 3 | * distinct 4 | * distinctKey 5 | * distinctUntilChanged 6 | * distinctUntilKeyChanged 7 | * elementAt 8 | * filter 9 | * first 10 | * ignoreElements 11 | * audit 12 | * auditTime 13 | * last 14 | * sample 15 | * sampleTime 16 | * single 17 | * skip 18 | * skipLast 19 | * skipUntil 20 | * skipWhile 21 | * take 22 | * takeLast 23 | * takeUntil 24 | * takeWhile 25 | * throttle 26 | * throttleTime -------------------------------------------------------------------------------- /operators/multi operators/README.md: -------------------------------------------------------------------------------- 1 | * cache 2 | * multicast 3 | * publish 4 | * publishBehavior 5 | * publishLast 6 | * publishReplay 7 | * share 8 | -------------------------------------------------------------------------------- /operators/transform operators/README.md: -------------------------------------------------------------------------------- 1 | 2 | * buffer 3 | * bufferCount 4 | * bufferTime 5 | * bufferToggle 6 | * bufferWhen 7 | * concatMap 8 | * concatMapTo 9 | * exhaustMap 10 | * expand 11 | * groupBy 12 | * map 13 | * mapTo 14 | * mergeMap 15 | * mergeMapTo 16 | * mergeScan 17 | * pairwise 18 | * partition 19 | * pluck 20 | * scan 21 | * switchMap 22 | * switchMapTo 23 | * window 24 | * windowCount 25 | * windowTime 26 | * windowToggle 27 | * windowWhen -------------------------------------------------------------------------------- /operators/transform operators/all-maps.md: -------------------------------------------------------------------------------- 1 | * concatMap: 拼接 2 | * mergeMap: 交错 3 | * switchMap: 源 Observable 发出值时会取消内部 Observable 先前的所有订阅 4 | 5 | 6 | 7 | 8 | 9 | concat / merge / switch 可以放在一起学习,每组都还有 concatAll / concatMap 这样的方法, 10 | 11 | 其中,concat 与 concatAll 的关系是: 12 | 13 | ![](https://github-riskers-blog.oss-cn-qingdao.aliyuncs.com/20181212170029.png) 14 | 15 | ----- 16 | 17 | 而 concatMap 其实就是 map + concatAll 的语法糖: 18 | 19 | ![](https://github-riskers-blog.oss-cn-qingdao.aliyuncs.com/20181212170106.png) 20 | -------------------------------------------------------------------------------- /operators/transform operators/scan-vs-reduce.md: -------------------------------------------------------------------------------- 1 | [stackblitz](https://stackblitz.com/edit/rxjs-hlhsnv?embed=1&file=index.ts) -------------------------------------------------------------------------------- /wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riskers/rxjs-note/3ddb866ec80d53b47e1780f0c7e1255c4076ce6e/wechat.jpg --------------------------------------------------------------------------------