译按:上文 翻译:0147-use-mutable-source.md 提到了
useSubscription,但是我却并不了解,因此继续翻译useSubscriptionREADME,顺便学习一下。
use-subscription
(它是)在并发模式下安全地管理订阅的 React hook。
这个功能可以被用于订阅单个值,该值一般仅在一个地方读取并且更新频繁(例如一个订阅地理位置 API 展示地图上的点的组件)。
什么时候我们应该 不 使用这个?
大部分其他情况有 更好的长期解决方案:
- Redux/Flux store 应该用 context API 替代。
- 很少更新的 I/O 订阅(例如通知)应该用一种像 react-cache 的机制替代。
- 像 Relay/Apollo 的复杂库应该手动管理订阅、用这个库背后的(如 这里 引用)某种程度上对库用法最优的相同技术。
并发模式下的局限
use-subscription 在并发模式下使用时安全的。然而,它通过有时降级成同步模式保证正确性,消除了并发渲染的好处。这是一个继承而来的局限、在 React 管理的状态队列之外保存状态和对一个变化事件的响应渲染中。
降级成同步模式的副作用是主线程可能周期性被阻塞(在计算密集型情况下),并且占位符可能比预期更早出现(在 I/O 密集型情况下)。
为了 完全兼容 并发渲染,包括 时间切片 和 React 挂起,建议的长期解决方案是选取上一章节描述的模式之一。
这个支持哪些订阅类型?
这个抽象能处理各种各样的订阅类型,包括:
- 像
HTMLInputElement的事件分发。 - 像 Relay
FragmentSpecResolver的自定义 pub/sub 组件。 - 像 RxJS
BehaviorSubject和ReplaySubject的 Observable 类型。(像 RxJSSubject或Observable的类型不支持,因为它们不提供在被触发后读取“当前”值的方法。)
注意 JavaScript promise 也 不支持 因为它们不提供同步读取“当前”值得方法。
安装
# Yarn
yarn add use-subscription
# NPM
npm install use-subscription
用法
配置订阅,你必须提供两个方法:getCurrentValue 和 subscribe。
为了避免每次调用这个 hook 时移除和重新添加订阅,传给这个 hook 的参数应该被记住。这可以通过用 useMemo() 包裹整个订阅,或者用 useCallback() 包裹单个回调来完成。
订阅事件分发器
下面例子展示 use-subscription 如何被用于订阅事件分发器比如 DOM 元素。
import React, { useMemo } from "react";
import { useSubscription } from "use-subscription";
// 在这个例子中,"input" 是一个事件分发器(例如一个 HTMLInputElement)
// 但是它可以是任意的触发事件和有可读当前值的对象。
function Example({ input }) {
// 记住值以避免每次调用这个 hook 时移除和重新添加订阅
const subscription = useMemo(
() => ({
getCurrentValue: () => input.value,
subscribe: callback => {
input.addEventListener("change", callback);
return () => input.removeEventListener("change", callback);
}
}),
// input 任意时间变化时再次订阅
// (例如我们获得一个要订阅的新 HTMLInputElement prop)
[input]
);
// 这个 hook 返回的值反映了 input 的当前值。
// 我们的组件将在值变化时自动重新渲染。
const value = useSubscription(subscription);
// 你的渲染输出在这里写...
}
订阅 observables
下面例子展示 use-subscription 如何被用于订阅某个 observables 类型(例如 RxJS BehaviorSubject 和 ReplaySubject)。
注意 不太可能支持所有的 observable 类型(例如 RxJS Subject 或 Observable)因为一些不提供在被触发后读取“当前”值的方法。
BehaviorSubject
const subscription = useMemo(
() => ({
getCurrentValue: () => behaviorSubject.getValue(),
subscribe: callback => {
const subscription = behaviorSubject.subscribe(callback);
return () => subscription.unsubscribe();
}
}),
// behaviorSubject 任意时间变化时再次订阅
[behaviorSubject]
);
const value = useSubscription(subscription);
ReplaySubject
const subscription = useMemo(
() => ({
getCurrentValue: () => {
let currentValue;
// Replay
// ReplaySubject 没有同步获取数据的 getter,
// 所以我们需要临时订阅以便获得最接近现在的值。
replaySubject
.subscribe(value => {
currentValue = value;
})
.unsubscribe();
return currentValue;
},
subscribe: callback => {
const subscription = replaySubject.subscribe(callback);
return () => subscription.unsubscribe();
}
}),
// replaySubject 任意时间变化时再次订阅
[replaySubject]
);
const value = useSubscription(subscription);