observable-hooks
用来连接普通值和 Observable
提供 hook 来连接 observable 和 React 状态
为什么需要在 hooks 中使用 Rxjs
因为 hooks 仅仅是为函数式组件提供了状态,但是对于一些异步操作,我们仍然需要一些异步的工具来减少工作
写在前面
-
observable-hooks 的 hooks 如果就接收一个函数作为参数,那么这个函数只会在组件初始化的时候执行一次
-
observable-hooks 返回的值在组件更新期间都不会发生改变
常用的 hooks
!useObservable
useObservable<TOutput>(init: function): Observable<TOutput>
useObservable<TOutput, TInputs>(
init: function,
inputs: TInputs
): Observable<TOutput>
在函数组件中创建一个流,每次组件更新返回的流的引用是不变的,也就是随着组件更新流不会每次都重新创建
-
接收一个初始化函数,函数只会被执行一次,函数返回的是一个 observable。该函数接收一个参数是由依赖数组构成的 observable
-
接收一个依赖数组,数组中任意一个元素的值发生变化流都会重新执行(当初始化函数中的流依赖了数组中的值时)
没有传递依赖数组的时候这个流只会在组件初始化的时候定义一次。
useLayoutObservable
与 useObservable 基本一样,不同的是底层使用 useLayoutEffect 监听改变。
useObservableCallback
useObservableCallback<TOutput, TInput, TParams>(
init: function,
selector?: undefined | function
): [function, Observable<TOutput>]
- init 函数接收的参数就是执行该 hooks 返回的 function 在调用时传入的参数构建的流,init 函数的返回值就是执行该 hooks 返回的元组的第二个值
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 中订阅的区别
- useSubscription 的回调中我们可以保证始终可以拿到最新的 state/props
- 当 Observable 改变时 useSubscription 会自动取消旧的并订阅新的。
- 并发模式安全,会避免旧的 observable 触发回调
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)];
-
第二个参数是初始值,也就是 hook 的第一个参数中的流没有发出值的时候,state 的默认值,不传则是 undefined
-
当直接传入一个 observable 是,返回值是一个 state
-
当传入一个 init 函数时,返回值是一个元组;元组的第一个参数是 state,第二个参数则是一个更新函数,调用该函数可以重新触发流执行。
-
init 函数接收一个 observable,这个 observable 是我们调用更新函数是传入的参数包装而来的,init 函数内部的流需要使用到这个 observable 才可以有调用更新函数,流重新执行,状态更新的效果
需要注意一下初始化的时候,如果没有调用回调,可能就没有 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"]>