¡Integre fácilmente rxjs en ganchos de reacción! análisis de código fuente de ganchos observables!

prefacio

Un antiguo colega mío acaba de unirse a una empresa estatal local en Chengdu (es demasiado envidiable salir del trabajo a las 5 en punto). Usaron la biblioteca de ganchos observables, que no he usado antes. Solo conozco un biblioteca llamada rxjs-hooks del equipo front-end de leetcode. Traté de ir al sitio web oficial para verla y descubrí que los ganchos observables tienen una mejor capacidad

Pero para ser honesto, los documentos escritos en el sitio web oficial son realmente difíciles de entender para las personas con un dominio promedio de rxjs. Así que este artículo está hecho para aprender con todos. Cada API tiene un caso en línea, que es conveniente para que todos jueguen solos.

Explicación del código fuente de la API principal

useObservable

Esta API generalmente se usa para monitorear los cambios de valor y luego devolver un Observable, que tiene la ventaja de ser receptivo como vue y mobx.

En pocas palabras: escuche los cambios de valor y luego genere una nueva secuencia, use este método, el caso es el siguiente:

Haga clic en el botón, dirección en línea:

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");

  // 监听 props 或 state 变化
  const enhanced$ = useObservable(
    (inputs$) => inputs$.pipe(map(([showPanel]) => showPanel + "world")),
    [showPanel]
  );
  return (
    <div className="App">
      <h1>{showPanel}</h1>
      <button
        onClick={() => {
          // 这个方法
          enhanced$.subscribe((value) => alert(value));
        }}
      >
        click
      </button>
    </div>
  );
};

export default App;
复制代码

Análisis de código fuente, código clave:

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
}
复制代码

useRefFn usa useRef para crear nuevos BehaviorSubject(entradas), en lugar de instanciar nuevos cada vez, podemos reutilizar el nuevo BehaviorSubject generado por primera vez. El código fuente es muy simple, tenga en cuenta que init() solo se llamará una vez, ya que sigue:

/**
 * 一个返回值的函数。 只会被调用一次
 */
 export function useRefFn<T>(init: () => T) {
  const firstRef = useRef(true)
  // 请注意init() 只会调用一次
  const ref = useRef<T | null>(null)
  if (firstRef.current) {
    firstRef.current = false
    ref.current = init()
  }
  return ref;
}
复制代码

Bueno, echemos un vistazo al código fuente de useObservable, primero cree un nuevo BehaviorSubject (inputs), las entradas son el segundo parámetro de useObservable, dependiendo de los datos, useObservable impulsará la transmisión nuevamente cuando estos datos cambien.

El código para volver a empujar es:

  useEffect(() => {
    if (firstEffectRef.current) {
      firstEffectRef.current = false
      return
    }
    inputs$Ref.current.next(inputs)
  }, inputs)
复制代码

Se puede ver que useEffect se usa para monitorear los cambios de entradas y luego input$Ref.current.next(inputs) para impulsar la transmisión nuevamente.

Finalmente, veamos esta oración.

  const source$Ref = useRefFn(() => init(inputs$Ref.current))
复制代码

Es pasar el nuevo flujo BehaviorSubject(inputs) a la función init.La función init es el primer parámetro de useObservable, que personalizamos.

useLayoutObservable

与 useObservable 基本一样,不同的是底下使用 useLayoutEffect 监听改变。

如果需要在下次浏览器绘制前拿到值可以用它, 所以源码跟我们之前是一样的,就是把useEffect改成了useLayoutEffect而已。

useObservableCallback

一言以蔽之,这个useObservableCallback一般用来给事件监听的,事件一变化就产生新的流。需要注意的是,需要自己手动去订阅。

案例如下(当input值变化时,注意看控制台信息变化): 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;
复制代码

源码如下:

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]
}
复制代码
  • 首先events$Ref就是一个new Subject
  • 然后定义一个消费流outputs$Ref,我们传入的自定义init函数第一个参数就是上一步的new Subject
  • callbackRef是一个注册函数,常用于给事件,也就是事件触发,就给outputs$Ref推送数据

当然需要回调函数传入多个参数才需要selector,大家现在只是入门,等用到的时候再了解不迟。

useSubscription

useSubscription说白了,就是subscribe的hooks而已。

源码如下:

源码只做了一个特殊处理需要注意,其他的不用看,就是subscrible

if (input$ !== argsRef.current[0]) {
     // stale observable
     return
}
复制代码
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
}
复制代码

useLayoutSubscription

与 useSubscription 一样,除了 subscription 是通过 useLayoutEffect 触发。

当需要在 DOM 绘制前拿到值时会有用。

尽量少用,因为其是在浏览器绘制前同步调用。过多的同步值产生会延长组件的 commit 周期。

useObservableState

Esta función se puede usar como useState o useReducer. Le sugiero que use useReducer en lugar de state para sus propios proyectos, porque generalmente su estado es clasificado. Por ejemplo, cuando solicita un dato, este dato es una variable, pero también está acompañado por el hecho de que la solicitud El estado de carga de los datos, la carga y los datos solicitados están integrados. ¿Por qué usar dos estados de uso? Parece realmente incómodo.

El caso más uno menos uno es el siguiente:

Código en línea: codesandbox.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" });
        }}
      >
        加一
      </button>
      <button
        onClick={() => {
          dispatch({ type: "DECREMENT" });
        }}
      >
        减一
      </button>
    </div>
  );
};

export default App;
复制代码

Entonces solo presentamos cómo usar useObservableState para implementar useReducer aquí, el siguiente es el código clave

  • estado es la información que queremos
  • La devolución de llamada pasará el valor que desea pasar al primer parámetro state$OrInit, que es nuestro flujo personalizado
  • useSubscription eventualmente devolverá el estado más reciente de los datos procesados ​​setState en la transmisión
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]

}
复制代码

final

Supongo que te gusta

Origin juejin.im/post/7079704126548312100
Recomendado
Clasificación