# 核心 API
# useObservable
useObservable<TOutput>(init: function): Observable<TOutput>
useObservable<TOutput, TInputs>(
init: function,
inputs: TInputs
): Observable<TOutput>
React 函数组件每次计算渲染时都会被调用。利用 useObservable
创建或转换 Observables 以避免操作被反复执行。
参数接受一个返回 Observable 的类 epic 函数 init
,以及一个可选的依赖数组。数组会被传到一个 Observable 并交给 init
函数。
注意
因为 init
函数只会执行一遍,在里面通过闭包直接访问其它变量可能会不安全。见注意事项。
依赖数组限制
依赖数组在内部将直接传给 useEffect
(opens new window),故需要遵循同样的限制。比如数组的长度不能改变。
类型参数:
TOutput
输出 Observable 的值类型TInputs
依赖元组
参数:
参数名 | 类型 | 描述 |
---|---|---|
init | (inputs$: Observable<TInputs>): Observable<TOutput> | 一个纯函数,接收一个 Observable,返回一个 Observable。 |
inputs | TInputs | 可选的依赖数组。当数组中的一个项发生改变时 init 中的 Observable 会产生一个值,其值为整个依赖数组。 |
返回值:
Observable<TOutput>
总会返回同一个 Observable。
例子:
interface CompProps {
isOpen: boolean
}
const Comp: React.FC<CompProps> = props => {
const [showPanel, setShowPanel] = useState(false)
// 监听 props 或 state 变化
const enhanced$ = useObservable(
inputs$ => inputs$.pipe(map(([isOpen, showPanel]) => isOpen && showPanel)),
[props.isOpen, showPanel]
)
}
创建 Observable:
const now$ = useObservable(
() => interval(1000).pipe(
map(() => new Date().toLocaleString())
)
)
操作 Observable:
// outers$ are created from other React-unrelated module
const enhanced$ = useObservable(() => outers$.pipe(mapTo(false)))
也可以结合一起:
const enhanced$ = useObservable(
inputs$ => isEven$.pipe(
withLatestFrom(inputs$),
map(([isEven, [isOpen]]) => isEven && isOpen)
),
[props.isOpen]
)
# useLayoutObservable
与 useObservable
基本一样,不同的是底下使用 useLayoutEffect
(opens new window) 监听改变。
如果需要在下次浏览器绘制前拿到值可以用它。
# useObservableCallback
useObservableCallback<TOutput, TInput, TParams>(
init?: function,
selector?: undefined | function
): [function, Observable<TOutput>]
返回一个回调函数以及一个事件 Observable。每当回调被调用,Observable 会产生一个值,其值为回调接收的第一个参数。
v2.1.0 添加自 v2.1.0
同时接受一个可选的选择函数以支持多参数事件。回调函数每次接受的所有参数将作为数组交给这个函数,返回一个值交给 Observable。
TIP
如果你需要直接得到 state 值而不是一个 Observable 见 useObservableState。
注意
因为 init
函数只会执行一遍,在里面通过闭包直接访问其它变量可能会不安全。见注意事项。
TIP
在 TypeScript 中如果想要回调不接受任何参数,用 void
类型定义而不是 undefined
。
类型参数:
TOutput
输出 Observable 的值类型TInput
输入 Observable 的值类型TParams
回调函数参数类型数组
参数:
参数名 | 类型 | 描述 |
---|---|---|
init | (inputs$: Observable<TInputs>): Observable<TOutput> | 一个纯函数,接收一个 Observable,返回一个 Observable。 |
selector | (args: TParams): TInput | 可选的函数,接受一个数组包含每次所有的参数,返回单一值。 |
返回值:
[(...args: TParams): void, Observable<TOutput>]
一个元组,包含一个回调和一个 Observable。
例子:
import { useObservableCallback, useSubscription } from 'observable-hooks'
const Comp = () => {
const [onChange, textChange$] = useObservableCallback<
string,
React.FormEvent<HTMLInputElement>
>(event$ => event$.pipe(
pluck('currentTarget', 'value')
)) // 或者直接用 "pluckCurrentTargetValue" 方法
useSubscription(textChange$, console.log)
return <input type="text" onChange={onChange} />
}
转换回调多个参数:
import { useObservableCallback, identity } from 'observable-hooks'
const [onResize, height$] = useObservableCallback<
number,
number,
[number, number]
>(identity, args => args[1])
// onResize 接收到 width 和 hegiht
// height$ 得到 height 的值
onResize(100, 500)
# useObservableRef
useObservableRef<TValue>(
initialValue: TValue
): [MutableRefObject<TValue>, BehaviorSubject<TValue>]
useObservableRef<TValue>(
initialValue: TValue | null
): [RefObject<TValue>, BehaviorSubject<TValue>]
useObservableRef<TValue = undefined>(
initialValue?: TValue
): [MutableRefObject<TValue | undefined>, BehaviorSubject<TValue | undefined>]
返回一个 ref 对象和一个 BehaviorSubject。每当 ref.current 改变,BehaviorSubject 会发出新的值。
v4.2.2 添加自 v4.2.2.类型参数:
TValue
Ref 值类型.
参数:
参数名 | 类型 | 描述 |
---|---|---|
initialValue | TValue | 可选的初始值。 |
返回值:
[MutableRefObject<TValue>, BehaviorSubject<TValue>]
一个元组,包含一个 ref 对象和一个 BehaviorSubject。
例子:
const Comp = () => {
const [elRef, el$] = useObservableRef(null)
useSubscription(el$, console.log)
return <div ref={elRef}>Content</div>
}
const Comp = () => {
const [ref, value$] = useObservableRef(0)
useSubscription(value$, console.log)
return <button onClick={() => ref.current++}>Click</button>
}
# 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>
订阅 Observable,在并行模式下安全。
接受可选的 observer 对象或者可选的 next
、 error
、 complete
回调。如果选择后者则必须保持顺序,用 undefined
或者 null
占位。
为什么不能直接用 useEffect
?
useEffect(
() => {
const subscription = input$.subscribe({
next: ...,
error: ...,
complete: ...,
})
return () => {
subscription.unsubscribe()
}
},
[input$]
)
因为:
- 回调闭包引用的值会过期,导致回调被调用时拿不到组件最新的值。
- 并行模式下不安全。Observable 改变时可能会产生状态不同步的“撕裂”现象。
v2.0.0 添加自 v2.0.0
。 useSubscription
会保证被调用的是最新的回调。你可以放心地在回调中通过闭包引用任何变量。
v2.3.4 添加自 v2.3.4
。 当 Observable 改变时 useSubscription
会自动取消旧的并订阅新的。
v3.0.0 添加自 v3.0.0
。 useSubscription
在并行模式下安全。它会避免过期的 Observable 触发回调。
v3.2.0 添加自 v3.2.0
。 useSubscription
可以接受一个 observer 对象。非常感谢 OliverJAsh 的贡献!
为了兼容并行模式,useSubscription
开始订阅 Observable 的时机是在渲染被绘制到屏幕之后的副作用阶段。故即使 Observable 在订阅时会产生同步的值,这些值也会在第一次渲染之后到达。另外也可以使用针对同步值优化的 useObservableEagerState
。
TIP
注意 observer 回调发生改变并不会导致被订阅的 Observable 产生新的值。这不太常见但如果需要这种效果可以通过 useObservable
以回调函数为值造一个 Observable。
异常处理
因为 RxJS 的设计,Observable 一旦产生了异常就会被销毁。你可以:
- 通过在子流中放置
catchError
(opens new window) 避免异常传达主流。 - 也可以将 Observable 作为 state,发生异常时换掉新的。
useSubscription
会自动切换。 - 自
v4.2.0
配合useRenderThrow
可以让 Observable 异常被 React 错误边界获取到。你可以在那里做处理并更换被销毁的 Observable。
类型参数:
TInput
Input value within Observable.
参数:
参数名 | 类型 | 描述 |
---|---|---|
input$ | Observable<TInput> | Input Observable. |
next | (value: TInput): void | null | undefined | Notify when a new value is emitted. |
error | (error: any): void | null | undefined | Notify when a new error is thrown. |
complete | (): void | null | undefined | Notify when the Observable is complete. |
返回值:
React.MutableRefObject<Subscription | undefined>
A ref object with the RxJS Subscription. the ref current
is undefined
on first rendering.
类型参数:
TInput
输入 Observable 的值类型
参数:
参数名 | 类型 | 描述 |
---|---|---|
input$ | Observable<TInput> | null | undefined | 输入 Observable |
observer | PartialObserver<TInput> | Observer |
返回值:
React.MutableRefObject<Subscription | undefined>
A ref object with the RxJS Subscription. the ref current
is undefined
on first rendering.
例子:
const subscription = useSubscription(events$, e => console.log(e.type))
On complete
const subscription = useSubscription(events$, null, null, () => console.log('complete'
Access closure:
const [debug, setDebug] = useState(false)
const subscription = useSubscription(events$, null, error => {
if (debug) {
console.log(error)
}
})
Invoke props callback
const subscription = useSubscription(events$, props.onEvent)
# useLayoutSubscription
与 useSubscription 一样,除了 subscription 是通过 useLayoutEffect
触发。
当需要在 DOM 绘制前拿到值时会有用。
尽量少用,因为其是在浏览器绘制前同步调用。过多的同步值产生会延长组件的 commit 周期。
# useObservableState
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]
并发模式下也能安全地从 Observable 中获取值。前往核心概念了解此设计原理。
它有两种使用方式:
- 传一个 Observable 与一个可选的初始值。
const output = useObservableState(input$, initialState)
- 传一个类 epic 的方法与一个可选的初始值.
const [output, onInput] = useObservableState( (input$, initialState) => input$.pipe(...), initialState )
这两种方式底层使用不同的 hooks,故请勿选了一种方式之后又通过改变参数切换到另一种方式。
在内部,可选的 initialState
会被传到 useState(initialState)
。故其可以是状态值,也可以是返回状态的函数,以便昂贵计算。
同时 initialState
(或它的返回值)会被传入到 init
函数。这会很有用如果你想实现 reducer 模式,这种模式下需要一个初始值。
为了兼容并行模式,useObservableState
开始订阅 Observable 的时机是在渲染被绘制到屏幕之后的副作用阶段。故即使 Observable 在订阅时会产生同步的值,这些值也会在第一次渲染之后到达。
注意
因为 init
函数只会执行一遍,在里面通过闭包直接访问其它变量可能会不安全。见注意事项。
异常处理
因为 RxJS 的设计,Observable 一旦产生了异常就会被销毁。你可以:
- 通过在子流中放置
catchError
(opens new window) 避免异常传达主流。 - 也可以将 Observable 作为 state,发生异常时换掉新的。
useSubscription
会自动切换。 - 自
v4.2.0
配合useRenderThrow
可以让 Observable 异常被 React 错误边界获取到。你可以在那里做处理并更换被销毁的 Observable。
TIP
如果希望针对产生同步值的 Observable 跳过初始的额外一次 re-rendering,可以看看 useObservableEagerState
。
TIP
在 TypeScript 中如果你希望回调接受空参数,请用 void
而不是 undefined
类型。
类型参数:
TState
Output state.
参数:
参数名 | 类型 | 描述 |
---|---|---|
input$ | Observable<TState> | 一个 Observable。 |
initialState | TState | (): TState | 可选的初始状态。可以是状态值或者返回状态值的函数。 |
返回值:
TState
状态值
类型参数:
TState
输出的状态TInput
输入的状态
参数:
参数名 | 类型 | 描述 |
---|---|---|
init | (input$: Observable<TInput>, initialState: TState): Observable<TState> | 一个纯函数,接收一个 Observable,返回一个 Observable。 |
initialState | TState | 可选的初始状态。可以是状态值或者返回状态值的函数。 |
返回值:
[TState, (input: TInput): void]
一个元组包含状态与输入回调。
例子:
接收一个 Observable:
const count$ = useObservable(() => interval(1000))
const count = useObservableState(count$, 0)
或者接收一个 init
函数:
const [text, updateText] = useObservableState<string>(
text$ => text$.pipe(delay(1000)),
''
)
输入和输出状态可以是不同的类型:
// 输入: string, 输出: boolean
const [isValid, updateText] = useObservableState<boolean, string>(text$ =>
text$.pipe(map(text => text.length > 1)),
false
)
事件监听模式:
import { pluckCurrentTargetValue, useObservableState } from 'observable-hooks'
function App(props) {
const [text, onChange] = useObservableState(pluckCurrentTargetValue, '')
return (
<input onChange={onChange} value={text} />
)
}
Reducer 模式:
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 })
)
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'DECREMENT', payload: 2 })
# useObservableEagerState
useObservableEagerState<TState>(
state$: Observable<TState>
): TState
专门优化针对从热的或纯的 Observable(如 BehaviorSubject
)中同步地取值,避免触发额外的初次重渲染。
这个 hook 会多次 subscribe Observable。第一遍是获取同步产生的值,然后马上 unsubscribe。第二遍是真正的 subscribe。在并行模式下如果渲染被打断可以还会出现多次 subscribe。
注意
如果 Observable 是冷的且带副作用,这些副作用会被执行至少两次!只有热的或纯的 Observable 可以安全取值。
类型参数:
TState
输出的状态
参数:
参数名 | 类型 | 描述 |
---|---|---|
state$ | Observable<TState> | 一个 Observable |
返回值:
TState
状态值
例子:
const text1$ = new BehaviorSubject('A')
// 'A'
const text1 = useObservableEagerState(text1$)
const text2$ = of('A', 'B', 'C')
// 'C'
const text2 = useObservableEagerState(text2$)
# useLayoutObservableState
v4.1.0 添加自 v4.1.0
。
与 useObservableState
基本一样,不同的是底下使用 useLayoutEffect
(opens new window) 监听改变。
与 useObservableEagerState
不一样,useObservableEagerState
会在第一次 React 渲染前同步获取值,而 useLayoutObservableState
是在 React 渲染之后同步获取值,useObservableState
则是在 React 渲染并绘制完成之后异步获取值。
如果需要在下次浏览器绘制前拿到值可以用它。
尽量少用,因为其是在浏览器绘制前同步调用。过多的同步值产生会延长组件的 commit 周期。
# useObservableGetState
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]
...
类似 lodash 的 get
。
v2.3.0 添加自 v2.3.0
。通过属性路径从 Observable 状态对象中获得值。
v3.0.0 添加自 v3.0.0
。初始状态必须提供。
只有最终得出的状态发生改变才会触发重渲染。
WARNING
不可访问的路径会触发异常。
类型参数:
TState
输出的状态值。
参数:
参数名 | 类型 | 描述 |
---|---|---|
state$ | Observable<TState> | 一个 Observable。 |
initialState | undefined | null | TState[...] | (() => TState[...]) | 初始状态。可以是状态值或者返回状态值的函数。 |
pA | keyof TState | Key of TState . |
pB | keyof TState[A] | Key of TState[A] . |
pC | keyof TState[A][B] | Key of TState[A][B] . |
... | ... | .... |
返回值:
TState[...]
初始的状态值或者经过路径得到 TState
的属性类型。
例子:
const state$ = of({ a: { b: { c: 'value' } } })
// 第一次渲染:'default'
// 第二次渲染:'value'
const text = useObservableGetState(state$, 'default', 'a', 'b', 'c')
# useObservablePickState
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] }
类似 lodash 的 pick
。
v2.3.2 添加自 v2.3.2
。从一个状态对象中挑选属性组合成新的对象。
v3.0.0 添加自 v3.0.0
。初始状态必须提供。
挑选出的属性任一发生变化都会触发重渲染。
WARNING
不可访问的路径会触发异常。
类型参数:
TState
输出的状态TKeys
挑选的状态键名
参数:
参数名 | 类型 | 描述 |
---|---|---|
state$ | Observable<TState> | 一个 Observable。 |
initialState | undefined | null | { [K in TKeys]: TState[K] } | (() => { [K in TKeys]: TState[K] }) | 初始状态。可以是状态值或者返回状态值的函数。 |
...path | Array<keyof TState> | 挑选的 TState 键名。 |
返回值:
{ [K in TKeys]: TState[K] }
Initial value or partial TState
.
例子:
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'
)
# useRenderThrow
useRenderThrow<TInput>(input$: Observable<TInput>): Observable<TInput>
捕获 Observable 异常并重新以 React 渲染异常抛出,从而被上层错误边界(Error Boundary)捕获处理。
v4.2.0 添加自 v4.2.0
.
类型参数:
TInput
Input Observable
参数:
参数名 | 类型 | 描述 |
---|---|---|
input$ | Observable<TInput> | An Observable. |
返回值:
Observable<TInput>
An Observable.
例子:
const state$ = useObservable(() => of(({ a: 'a', b: 'b', c: 'c', d: 'd' })))
const enhanced$ = useRenderThrow(state$)
Suspense →