This is a RxJS tutorial for beginners.

What Is RxJS

Reading the official documentation, it is easy to conclude that RxJS is a good solution to the asynchrony/event combination problem.

At this point I have a question. Shouldn’t I use Promise (async/await) for async problems? As for events, wouldn’t it be easy to resolve with frameworks (React, Vue, Angular2, etc.)?

Anyway, let’s watch Hello World first. (I want to watch the DEMO)

Rx’s Hello World

// auto-complete
const Observable = Rx.Observable  
const input = document.querySelector('input')

const search$ = Observable.fromEvent(input, 'input')  
  .map(e => e.target.value)
  .filter(value => value.length >= 1)
  .throttleTime(100)
  .distinctUntilChanged()
  .switchMap(term => Observable.fromPromise(wikiIt(term)))
  .subscribe(
    x => renderSearchResult(x),
    err => console.error(err)
  )
Copy the code

The above code does the following:

  • Listening to theinputElements of theinputThe event
  • Once it happens, put the event objectemappinginputThe value of the element
  • It then filters out values whose length is less than1
  • And I set up onethrottle(throttle), the interval between two inputs shall not exceed100Milliseconds is a valid input
  • If the value is equal to the most recent value in the past, ignore it
  • Finally, you get the value and call one of Wikipedia’s apis
  • And last but not least, needsubscribeTo get the data back from the API

Doesn’t it look cool? I really want to learn! An auto-complete component is completed in just a few lines of code.

How It Works

What does that code mean? How exactly does RxJS work? How to solve the problem of asynchronous composition?

Observable

Rx provides a data type called Observable, which is compatible with the ECMAScript Draft Observable Spec Proposal standard. It is the most core data type of Rx, which combines Observer Pattern and Iterator Pattern.

So what exactly is an Observable?

An Observable is an asynchronous array. ( —> 2 minute introduction to rx )

Imagine an array + timeline = Observable.

The value of the array element is emitted at some point in the future, but we don’t care about that point because we subscribe to the array using observer mode, and as soon as it emits a value, it is automatically pushed to us.

Let’s use the graph to show it again:

--a---b-c--d-----e--|-->  
Copy the code

This kind of diagram is called a marble diagram. We can convert the ASCII marble image to SVG: ASCII -> SVG.

– said the timeline, the value of the said a ~ e emit | indicates the stream has ended. For example, the click event is represented in the figure above: A for the first click, B for the second click, and so on.

If you think the name Observable isn’t cool enough, you can call it Stream, because its marble maps are like Steam. So, I’ll always call an Observable a stream.

Operators

So, how do we operate on stream? How do I group multiple streams together?

An Observable is an asynchronous array. Don’t arrays in JavaScript have a lot of built-in methods? Such as Map, filter, reduce and so on. Likewise, observables have their own methods, called operators. For example, in the Rx’s Hello World example above, map, Filter, throttleTime, distinctUntilChanged, and many more useful operators.

With so many RxJS operators, how can we learn? Is simple:

Category + draw marble map + look at examples + select

Now let’s draw the marble diagram of the Hello World example above.

const search$ = Observable.fromEvent(input, 'input')  
  .map(e => e.target.value)
  .filter(value => value.length >= 1)
  .throttleTime(100)
  .distinctUntilChanged()
  .switchMap(term => Observable.fromPromise(wikiIt(term)))
  .subscribe(
    x => renderSearchResult(x),
    err => console.error(err)
  )
Copy the code

Suppose the input is 5 times, each time with a value of: A, ab, c, d, c, and the interval between the third input of C and the fourth input of D is less than 100ms:

---i--i---i-i-----i---|--> (input)
        map

---a--a---c-d-----c---|-->
      b
        filter

---a--a---c-d-----c---|-->
      b
      throttleTime

---a--a---c-------c---|-->
      b
  distinctUntilChanged

---a--a---c----------|-->
      b
    switchMap

---x--y---z----------|-->  
Copy the code

Would you believe me if I told you that the quickest way to learn RxJS is to “learn to see and draw marble maps”?

Learn By Doing

Now, let’s combine the above knowledge to implement a simple Canvas palette.

According to the Canvas API, we need to know the coordinates of the two points in order to draw a line.

Step 1

(I want to seeDEMO )

So, what we need to do now is create a stream about mouse movements. So we go to the document to find the corresponding operator category, Creation Operators, and get fromEvent.

const canvas = document.querySelector('canvas')

const move$ = Rx.Observable.fromEvent(canvas, 'mousemove')  
Copy the code

Corresponding Marble map:

--m1---m1-m2--m3----m4---|-->  (mousemove)
Copy the code

Next, we need to get the coordinates of each mouse movement. In other words, we need to transform stream. Operator documentation of the corresponding category: Transformation Operators –> Map.

const move$ = Rx.Observable.fromEvent(canvas, 'mousemove')  
  .map(e => ({ x: e.offsetX, y: e.offsetX }))
Copy the code

Marble map at this point:

- m1 - m2, m3, m4, m5 | -- - > (mousemove) map - x1 - x2, x3, x4, x5 | -- - > (point coordinates)Copy the code

And then, how do I get the coordinates of the two points? We need to transform the stream again. Operator documentation of the corresponding category: Transformation Operators –> bufferCount.

const move$ = Rx.Observable.fromEvent(canvas, 'mousemove')  
  .map(e => ({ x: e.offsetX, y: e.offsetY }))
  .bufferCount(2)
Copy the code

Marble figure:

- m1 - m2, m3, m4, m5 | -- - > (mousemove) map - x1 - x2, x3, x4, x5 | -- - > (point coordinates) bufferCount (2) -- -- -- -- -- -- -- the x1 -- -- -- -- -- the x3, x5 - | -- - > (two coordinates) x2 x4Copy the code

However, you can see that the line segment that you draw is discontinuous. Why is that? I don’t know!! Let’s take a look at what others have done: Canvas Paint.

Step 2

Let me see it firstDEMO )

Instead of transforming the stream, we combine the two streams together. See the documentation Combination Operators –> ZIP and Filtering Operators –> Skip

const move$ = Rx.Observable.fromEvent(canvas, 'mousemove')  
  .map(e => ({ x: e.offsetX, y: e.offsetY }))

const diff$ = move$  
  .zip(move$.skip(1), (first, sec) => ([ first, sec ]))
Copy the code

Marble map at this point:

--x1---x2-x3--x4----x5---|-->  (move$)
        skip(1)
-------x2-x3--x4----x5---|-->  



--x1---x2-x3--x4----x5---|-->  (move$)
-------x2-x3--x4----x5---|-->  
        zip

-------x1-x2--x3----x4---|-->  (diff$)
       x2 x3  x4    x5
Copy the code

Thus, the diff$emit value will be (x1, x2), (x2, x3), (x3, x4)… Now, as you move the mouse, you can draw beautiful lines.

Step 3

(I want to seeDEMO )

That’s when I realized why bufferCount didn’t work. Let’s compare:

-------x1-----x3----x5---|---> (bufferCount)
       x2     x4

-------x1-x2--x3----x4---|-->  (diff$)
       x2 x3  x4    x5
Copy the code

BufferCount emit values are (x1, x2), (x3, x4)… There’s a gap between x2 and x3. That’s why line segments are discontinuous.

And then if you look at the bufferCount document, you’ll see that you can do the same thing with bufferCount(2, 1). This way, we don’t need to use zip to combine two streams. Cool ~

const move$ = Rx.Observable.fromEvent(canvas, 'mousemove')  
  .map(e => ({ x: e.offsetX, y: e.offsetX }))
  .bufferCount(2, 1)
Copy the code

Marble map at this point:

- m1 - m2, m3, m4, m5 | -- - > (mousemove) map - x1 - x2, x3, x4, x5 | -- - > (point coordinates) bufferCount (2, 1) -- -- -- -- -- -- -- the x1, x2, x3, x4 - | -- - > (two coordinates) x2 x3 x4 x5Copy the code

Step 4

I want to see itDEMO )

Next, we want to achieve “only when the mouse is down, can draw, otherwise not”. First we need to create two streams for mouse actions.

const down$ = Rx.Observable.fromEvent(canvas, 'mousedown')  
const up$ = Rx.Observable.fromEvent(canvas, 'mouseup')  
Copy the code

When the mouse is pressed down, we need to transform it to stream for mouse movement until the mouse is released. See the documentation Transformation Operators –> switchMapTo.

down$.switchMapTo(move$)  
Copy the code

Marble map at this point:

--d---d-d-----d---d--|-->  (mousedown)
      switchMapTo

--m---m-m-----m---m--|--> 
Copy the code

At this point, the mouse release we can continue to draw, which is obviously not what we want. It would be tempting to use the takeUntil operator, but this would be wrong because it would destroy the stream complete.

Let’s take a look at what others have done: Canvas Paint.

Step 5

I just want to seeDEMO )

Here’s the idea:

Combine up$and down$into a new stream, but in order to distinguish them, we need to transform them into the new stream first. See the document Combination Operators –> Merge. Transformation Operators –> Map.

const down$ = Rx.Observable.fromEvent(canvas, 'mousedown')  
  .map(() => 'down')
const up$ = Rx.Observable.fromEvent(canvas, 'mouseup')  
  .map(() => 'up')

const upAndDown$ = up$.merge(down$)  
Copy the code

Take a look at their Marble image:

--d--d-d----d--d---|-->  (down$)
----u---u-u------u-|-->  (up$)
      merge

--d-ud-du-u-d--d-u-|-->  (upAndDown$)
Copy the code

At this point, we change upAndDown$. Move $if it is down, or an empty stream if it is not. See the document Creation Operators –> empty. Transformation Operators –> switchMap.

upAndDown$  
  .switchMap(action =>
    action === 'down' ? move$ : Rx.Observable.empty()
  )
Copy the code

Marble map you want:

--d-ud-du-u-d--d-u-|-->  (upAndDown$)
    switchMap

--m-em-me-e-m--m-e-|-->
Copy the code

In fact, this Canvas palette is not very difficult to implement without RxJS. But when we extend it to a “draw and GUESS” approach, handling asynchrony with RxJS becomes easier. For example, adding new toolbars (palette, undo…) , instant messaging (sync palette, chat)…

Also, if you want to implement a few little things while learning RxJS:

Production

How do you apply RxJS to a real-world web application? How does it fit into the current popular framework?

Vue

You can use RxJS directly in various Lifecycle Hooks.

For example, initialize an Observable when created and unsubscribe an Observable when beforeDestroy. (See DEMO)

new Vue({ el: '#app', data: { time: "'}, created () { this.timer$ = Rx.Observable.interval(1000) .map(() => new Date()) .map(d => moment(d).format('hh:mm:ss')) .subscribe(t => { this.time = t }) }, beforeDestroy () { this.timer$.unsubscribe() } })Copy the code

Vue-rx already does the dirty work for us. It automatically subscribes and unsubscribes Observable: vue.js + RxJS binding mixin in 20 lines at init and beforeDestroy, respectively.

Therefore, we can write an Observable directly to data: vue-rx/example.html.

React

Similarly, React can call RxJS from lifecycle hooks of its component: fully reactive- React. You can also bind Observable to state using rxjs-react-Component. You can use this redux-oServable if you combine it with Redux.

Angular2

RxJS is already standard with Angular2. See the corresponding document Angular2-server Communication for more information.

More on RxJS integration: RxJS Community.

You Might Not Need RxJS

When to Use RxJS

  • Multiple complex asynchrony or event combinations
  • Processing multiple data sequences (in some order)

I don’t think you should use RxJS if you don’t have problems with asynchrony, because Promise already solves simple asynchrony problems. What’s the difference between a Promise and an Observable? Look at Promise VS Observable.

Seriously, what business scenarios do RxJS apply to in real production? What scenarios require multiple asynchronous combinations? The game? Instant messaging? There are also special businesses. Am I writing too little business? When I write my business, I write for the sake of writing, not abstracting them.

I am also interested in Teambition’s thinking on RxJS: Xufei – Associated computing of data – Comments by Brooooooklyn & Xufei – Thinking about technology stacks for current single page applications.

Let’s learn about RxJS!