preface

One of my former colleagues just joined a local state-owned enterprise in Chengdu. They used the library of Observable hooks. I have never used it before, but I only know the library of Rxjs-hooks made by leetCode front-end team, so I tried to check the official website. It turns out that observabily-hooks are actually better at it

But to tell the truth, the official website to write the document for RXJS proficiency of ordinary people really difficult to understand, I also count them, simply look at the source code to understand. Thus this article, and you learn together. Each API has an online case for you to play with.

Core API source code explanation

useObservable

This API listens for changes in values and returns an Observable, which has the advantage of being responsive like Vue or Mobx.

In a nutshell: listen for a value change and generate a new stream, in this case:

Click button, online address:

Codesandbox. IO/s/sharp – haw…

import "./styles.css";
import { useObservable } from "observable-hooks";
import { map } from "rxjs";
import { useState } from "react";

const App = (props) = > {
  const [showPanel] = useState("hello");

  // Listen for props or state changes
  const enhanced$ = useObservable(
    (inputs$) = > inputs$.pipe(map(([showPanel]) = > showPanel + "world")),
    [showPanel]
  );
  return (
    <div className="App">
      <h1>{showPanel}</h1>
      <button
        onClick={()= >{// This method enhanced$.subscribe((value) => alert(value)); }} > click</button>
    </div>
  );
};

export default App;
Copy the code

Source code analysis, key code:

export function useObservable(init, inputs? : [...TInputs]) :Observable<TOutput> {

  const inputs$Ref = useRefFn(() = > new BehaviorSubject(inputs))
  const source$Ref = useRefFn(() = > init(inputs$Ref.current))

  const firstEffectRef = useRef(true)
  useEffect(() = > {
    if (firstEffectRef.current) {
      firstEffectRef.current = false
      return
    }
    inputs$Ref.current.next(inputs)
  }, inputs)

  return source$Ref.current
}
Copy the code

The new BehaviorSubject(Inputs) of the new BehaviorSubject is instantiated using the useRef simply init() :

/** * a function that returns a value. */ will only be called once
 export function useRefFn<T> (init: () => T) {
  const firstRef = useRef(true)
  // Note that init() is called only once
  const ref = useRef<T | null> (null)
  if (firstRef.current) {
    firstRef.current = false
    ref.current = init()
  }
  return ref;
}
Copy the code

First, create a new BehaviorSubject(Inputs), which is the second parameter of the useObservable. Inputs depend on the data, so the useObservable re-pushes the stream.

The code for the re-push is:

  useEffect(() = > {
    if (firstEffectRef.current) {
      firstEffectRef.current = false
      return
    }
    inputs$Ref.current.next(inputs)
  }, inputs)
Copy the code

Inputs $ref.current. Next (Inputs) : useEffect: inputs$ref.current.

Finally, let’s look at this sentence

  const source$Ref = useRefFn(() = > init(inputs$Ref.current))
Copy the code

The new BehaviorSubject(Inputs) is passed to the Init function, which is the first parameter of the useObservable.

useLayoutObservable

It’s basically the same as useObservable, but uses useLayoutEffect underneath to listen for changes.

You can use it if you need to get the value before the next browser draws, so the source code is the same as before, just change useEffect to useLayoutEffect.

useObservableCallback

In short, this useObservableCallback is generally used to monitor events, which generate new streams as they change. Note that you need to subscribe manually.

Case is as follows (when the input value change, look at the console information change) : codesandbox. IO/s/affection…

import "./styles.css";
import { pluck, map } from "rxjs";
import { useObservableCallback } from "observable-hooks";
import { useEffect } from "react";

const App = (props) = > {
  const [onChange, outputs$] = useObservableCallback((event$) = >
    event$.pipe(pluck("currentTarget"."value"))); useEffect(() = > outputs$.subscribe((v) = > console.log(v)));
  return <input type="text" onChange={onChange} />;
};

export default App;
Copy the code

The source code is as follows:

export function useObservableCallback(init, selector) {
  const events$Ref = useRefFn(new Subject())
  const outputs$Ref = useRefFn(() = > init(events$Ref.current))
  const callbackRef = useRef((. args) = > {
    events$Ref.current.next(selector ? selector(args) : args[0])})return [callbackRef.current, outputs$Ref.current]
}
Copy the code
  • Events $Ref is a new Subject
  • Then we define a consumption stream outputs$Ref. The custom init function we pass in as the first argument is the new Subject from the previous step
  • The callbackRef is a registration function that is often used to push data to the outputs$Ref, the event trigger

Of course you have to pass multiple arguments to a callback to get a selector, but you’re just getting started, and you’ll see that when you use it.

useSubscription

UseSubscription and scription are adapted from hooks of subscribe.

The source code is as follows:

There is only a special processing of the source code that needs to be paid attention to, and the rest is not required to look at, which is subscrible

if(input$ ! == argsRef.current[0]) {
     // stale observable
     return
}
Copy the code
export function useSubscriptionInternal(
  args
) {
  const argsRef = useRef(args)
  const subscriptionRef = useRef()

  useEffect(() = > {
    argsRef.current = args
  })

  useEffect(() = > {
    const input$ = argsRef.current[0]

    const subscription = input$.subscribe({
      next: value= > {
        if(input$ ! == argsRef.current[0]) {
          // stale observable
          return
        }
        const nextObserver =
          argsRef.current[1].next ||
          argsRef.current[1]
        if (nextObserver) {
          return nextObserver(value)
        }
      },
      error: error= > {
        if(input$ ! == argsRef.current[0]) {
          // stale observable
          return
        }
        const errorObserver =
          argsRef.current[1].error ||
          argsRef.current[2]
        if (errorObserver) {
          return errorObserver(error)
        }
        console.error(error)
      },
      complete: () = > {
        if(input$ ! == argsRef.current[0]) {
          // stale observable
          return
        }
        const completeObserver =
          argsRef.current[1].complete ||
          argsRef.current[3]
        if (completeObserver) {
          return completeObserver()
        }
      }
    })

    subscriptionRef.current = subscription

    return () = > {
      subscription.unsubscribe()
    }
  }, [args[0]])

  return subscriptionRef
}
Copy the code

useLayoutSubscription

Like useSubscription, except subscription is triggered by useLayoutEffect.

This is useful when you need to get the value before the DOM is drawn.

Use it sparingly, as it is called synchronously before the browser draws. Too many synchronization values can prolong the component’s commit cycle.

useObservableState

This function can be used as useState or useReducer. I suggest you use useReducer instead of state in your own projects, because your state is generally classified. For example, when you request a data, the data is a variable, but the loading state when you request data is also accompanied. Loading and the requested data are one and the same, so why use two useState files?

The case plus one minus one is as follows:

IO/S/kind-JASp…

import "./styles.css";
import { scan } from "rxjs";
import { useObservableState } from "observable-hooks";

const App = (props) = > {
  const [state, dispatch] = useObservableState(
    (action$, initialState) = >
      action$.pipe(
        scan((state, action) = > {
          switch (action.type) {
            case "INCREMENT":
              return {
                ...state,
                count:
                  state.count + (isNaN(action.payload) ? 1 : action.payload)
              };
            case "DECREMENT":
              return {
                ...state,
                count:
                  state.count - (isNaN(action.payload) ? 1 : action.payload)
              };
            default:
              return state;
          }
        }, initialState)
      ),
    () = > ({ count: 0}));return (
    <div className="App">
      <h1>{state.count}</h1>
      <button
        onClick={()= >{ dispatch({ type: "INCREMENT" }); }} > plus one</button>
      <button
        onClick={()= >{ dispatch({ type: "DECREMENT" }); }} > minus one</button>
    </div>
  );
};

export default App;
Copy the code

So we will only introduce how to use the useObservableState to implement useReducer

  • State is the data we want
  • Callback will pass the value you want to pass to the first argument, state$OrInit, in our custom stream
  • UseSubscription eventually returns its processed data setState to the latest state
useObservableStateInternal(state$OrInit, initialState){
    const init = state$OrInit
    const [state, setState] = useState(initialState)

    const input$Ref = useRefFn(new Subject())

    const state$ = useRefFn(() = > init(input$Ref.current, state)).current
    const callback = useRef((state) = >
      input$Ref.current.next(state)
    ).current

    useSubscription(state$, setState)

    return [state, callback]

}
Copy the code

The end of the