observable-hooks

2021-12-01  本文已影响0人  前端小白的摸爬滚打

用来连接普通值和 Observable
提供 hook 来连接 observable 和 React 状态

为什么需要在 hooks 中使用 Rxjs

因为 hooks 仅仅是为函数式组件提供了状态,但是对于一些异步操作,我们仍然需要一些异步的工具来减少工作

写在前面

常用的 hooks

!useObservable

useObservable<TOutput>(init: function): Observable<TOutput>
useObservable<TOutput, TInputs>(
  init: function,
  inputs: TInputs
): Observable<TOutput>

在函数组件中创建一个流,每次组件更新返回的流的引用是不变的,也就是随着组件更新流不会每次都重新创建

没有传递依赖数组的时候这个流只会在组件初始化的时候定义一次。

useLayoutObservable

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

useObservableCallback

useObservableCallback<TOutput, TInput, TParams>(
  init: function,
  selector?: undefined | function
): [function, Observable<TOutput>]

useObservableCallback 函数返回的值在组件更新的时候保持不变

init 函数只会在组件创建的时候执行一次

!useSubscription

useSubscription<TInput>(
  input$: Observable<TInput>,
  observer?: PartialObserver<TInput>
): React.MutableRefObject<Subscription | undefined>
useSubscription<TInput>(
  input$: Observable<TInput>,
  next?: function | null | undefined,
  error?: function | null | undefined,
  complete?: function | null | undefined
): React.MutableRefObject<Subscription | undefined>

订阅一个流,这个流改变/重新发出值的时候会被重新订阅,所以传入的流需要在组件的生命周期中引用不会发生改变,推荐是使用 useObservable/useRef 来创建这个流

使用 useSubscription 订阅和在 useEffect 中订阅的区别

useLayoutSubscription

和 useSubscription 使用方式相同,只不过底层变化监听是在 useLayoutEffect 中实现的

!useObservableState

自动订阅 observable 得到 state,state 是订阅 observable 是发出的值,当订阅的流发出新的值或者改变的时候会重新订阅,因为是 state,所以值变化可以引起页面的刷新

传入的 observable 需要在组件的生命周期期间相同,否则会导致每次组件更新都会重新订阅,造成死循环

传入一个 observable 时,这个 observable 需要使用 useRef/useObservable;传入的是一个函数时,函数返回的 observable 则可以是一个普通的 observable,因为函数只会执行一次

useObservableState<TState>(
  input$: Observable<TState>
): TState | undefined
useObservableState<TState>(
  input$: Observable<TState>,
  initialState: TState | (() => TState)
): TState
useObservableState<TState, TInput = TState>(
  init: (input$: Observable<TInput>) => Observable<TState>
): [TState | undefined, (input: TInput) => void]
useObservableState<TState, TInput = TState>(
  init: (
    input$: Observable<TInput>,
    initialState: TState
  ) => Observable<TState>,
  initialState: TState | (() => TState)
): [TState, (input: TInput) => void]

当传入一个 init 函数时的源码

const init = state$OrInit;
const [state, setState] = (useState < TState) | (undefined > initialState);

const input$Ref = useRefFn < Subject < TInput >> getEmptySubject;

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

useSubscription(state$, setState);
useDebugValue(state);
return [(state, callback)];

需要注意一下初始化的时候,如果没有调用回调,可能就没有 state 值,此时我们可以给 input$.pipe(startWith(value))来帮助我们在组件初始化的时候执行一次流

useObservableEagerState

useLayoutObservableState

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

!useObservableGetState

类似于 lodash 的 get
通过属性路径从 Observable 状态对象中获得值。

useObservableGetState<TState>(
  state$: Observable<TState>,
  initialState: TState | (() => TState)
): TState
useObservableGetState<TState, A extends keyof TState>(
  state$: Observable<TState>,
  initialState: TState[A] | (() => TState[A]),
  pA: A
): TState[A]
useObservableGetState<
  TState,
  A extends keyof TState,
  B extends keyof TState[A]
>(
  state$: Observable<TState>,
  initialState: TState[A][B] | (() => TState[A][B]),
  pA: A,
  pB: B
): TState[A][B]
...
const state$ = of({ a: { b: { c: "value" } } });

// 第一次渲染:'default'
// 第二次渲染:'value'
const text = useObservableGetState(state$, "default", "a", "b", "c");

!useObservablePickState

类似于 lodash 的 pick

useObservablePickState<
  TState,
  TKeys extends keyof TState,
  TInitial extends null | undefined | void
>(
  state$: Observable<TState>,
  initialState: TInitial,
  ...keys: TKeys[]
): { [K in TKeys]: TState[K] } | TInitial
useObservablePickState<TState, TKeys extends keyof TState>(
  state$: Observable<TState>,
  initialState:
    | { [K in TKeys]: TState[K] }
    | (() => { [K in TKeys]: TState[K] }),
  ...keys: TKeys[]
): { [K in TKeys]: TState[K] }
const state$ = of({ a: "a", b: "b", c: "c", d: "d" });

// 第一次渲染:{ a: '', b: '', c: '' }
// 第二次渲染:{ a: 'a', b: 'b', c: 'c' }
const picked = useObservablePickState(
  state$,
  () => ({ a: "", b: "", c: "" }),
  "a",
  "b",
  "c"
);

辅助方法

identity

identity<T>(value: T): T

返回第一个参数

function identity(value) {
  return value;
}

pluckFirst

pluckFirst<TArr>(inputs$: Observable<TArr>): Observable<TArr[0]>

接收一个数组流,返回数组第一个元素构成的流

useRefFn

useRefFn<T>(init: function): MutableRefObject<T>

接收一个函数,这个函数只会被调用一次,返回值就是使用这个函数的返回值作为 current 属性的 ref 对象

接收一个函数,将函数的返回值作为 ref 的 current 的值,返回这个 ref

默认 useRef 会在每次组件更新的时候都会重新执行,只不过不会更新引用

所以 useRef 如果传递一个函数,则每次更新传入的函数都是一个新的函数,但是 useRef 并不会用,同样的,传入一个 new 创建的实例时,每次组件更新也都是一个新的实例,会导致重复创建了很多对象。但是这些都没有关系,虽然每次都是新函数/新对象,但是我们的 current 并不会因为 useRef 的重新执行就更新上面的值。current 上的值在没有更新的情况下,始终保存的是组件创建时的 useRef 传入的值

useForceUpdate

调用这个 hook 可以使组件重新渲染,内部其实是定义了一个 state,每次调用这个 hook 都会改变这个 state 然后导致组件重新渲染

pluckCurrentTargetValue

pluckCurrentTargetValue<TEvent>(
  event$: Observable<TEvent>
): Observable<TEvent["currentTarget"]["value"]>

pluckCurrentTargetChecked

pluckCurrentTargetChecked<TEvent>(
  event$: Observable<TEvent>
): Observable<TEvent["currentTarget"]["checked"]>
上一篇下一篇

猜你喜欢

热点阅读