prefácio
Um ex-colega meu acabou de ingressar em uma empresa estatal local em Chengdu (é muito invejável sair do trabalho às 5 horas). Eles usaram a biblioteca de ganchos observáveis, que eu não usei antes. biblioteca chamada rxjs-hooks da equipe de front-end do leetcode. , tentei ir ao site oficial para vê-lo e descobri que os ganchos observáveis são realmente melhores em capacidade
Mas para ser honesto, os documentos escritos no site oficial são realmente difíceis de entender para pessoas com proficiência média em rxjs. Portanto, este artigo é feito para aprender com todos. Cada API tem um caso online, o que é conveniente para todos jogarem sozinhos.
Explicação do código-fonte da API principal
useObservável
Essa API geralmente é usada para monitorar alterações de valor e, em seguida, retornar um Observable, que tem a vantagem de ser responsivo como vue e mobx.
Em poucas palavras: ouça as mudanças de valor e depois gere um novo fluxo, use este método, o caso é o seguinte:
Clique no botão, endereço online:
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álise de código-fonte, código-chave:
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 fazer um novo BehaviorSubject(inputs), ao invés de instanciar novo toda vez, podemos reutilizar o novo BehaviorSubject gerado pela primeira vez. O código fonte é muito simples, observe que init() só será chamado uma vez, pois segue:
/**
* 一个返回值的函数。 只会被调用一次
*/
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;
}
复制代码
Bem, vamos olhar para o código-fonte useObservable, primeiro crie um novo BehaviorSubject(inputs), inputs é o segundo parâmetro de useObservable, dependendo dos dados, useObservable enviará o fluxo novamente quando esses dados forem alterados.
O código para reenviar é:
useEffect(() => {
if (firstEffectRef.current) {
firstEffectRef.current = false
return
}
inputs$Ref.current.next(inputs)
}, inputs)
复制代码
Pode-se ver que useEffect é usado para monitorar as mudanças de entradas e, em seguida, inputs$Ref.current.next(inputs) para enviar o fluxo novamente.
Finalmente, vamos olhar para esta frase
const source$Ref = useRefFn(() => init(inputs$Ref.current))
复制代码
É passar o novo stream BehaviorSubject(inputs) para a função init.A função init é o primeiro parâmetro de useObservable, que é personalizado por nós.
useLayoutObservável
与 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 função pode ser usada como useState ou useReducer. Sugiro que você use useReducer em vez de state para seus próprios projetos, porque geralmente seu estado é classificado. Por exemplo, quando você solicita um dado, esse dado é uma variável, mas também é acompanhado pelo fato de que o pedido O estado de carregamento dos dados, o carregamento e os dados solicitados estão integrados. Por que usar dois useStates? Parece realmente estranho.
O caso mais um menos um é o seguinte:
Código online: 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;
复制代码
Portanto, apenas apresentamos como usar useObservableState para implementar useReducer aqui, o seguinte é o código-chave
- estado são os dados que queremos
- O retorno de chamada passará o valor que você deseja passar para o primeiro parâmetro state$OrInit, que é nosso fluxo personalizado
- useSubscription eventualmente retornará o estado mais recente do conjunto de dados processado no fluxo
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]
}
复制代码
fim