├── .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 | 
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 | 
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 | 
119 |
120 | 
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 | 
253 |
254 | 因为 React API 的关系,只能通过 Subject 帮助我们把 React Event 转化为 Observable。但绝大多数的情況我們是可以通過 Observable.create 來做到這件事,像下面這樣
255 |
256 | 
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 | 
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 | 
14 |
15 | -----
16 |
17 | 而 concatMap 其实就是 map + concatAll 的语法糖:
18 |
19 | 
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
--------------------------------------------------------------------------------