useSnapshot
预计阅读时间: 5 分钟
订阅 State 容器的钩子。
useSnapshot 是专门为 State 容器设计的高级订阅钩子。它使用 useSyncExternalStore 来确保在并发模式下安全地订阅状态变更,并提供两个重载——不带选择器用于完整状态访问,或带选择器用于派生状态订阅。
示例
不带选择器
import { useSnapshot } from '@videojs/react';
import { stateContainer } from './myContainer';
function FullState() {
const state = useSnapshot(stateContainer);
// state 是完整的 State 类型
return (
<div>
<p>Current time: {state.currentTime}</p>
<p>Duration: {state.duration}</p>
<p>Paused: {String(state.paused)}</p>
</div>
);
}
带选择器
import { useSnapshot } from '@videojs/react';
import { stateContainer } from './myContainer';
function DerivedState() {
// 选择并派生状态
const { remaining, progress } = useSnapshot(
stateContainer,
(state) => ({
remaining: state.duration - state.currentTime,
progress: state.duration > 0
? (state.currentTime / state.duration) * 100
: 0,
})
);
return (
<div>
<p>Remaining: {remaining.toFixed(1)}s</p>
<p>Progress: {progress.toFixed(1)}%</p>
</div>
);
}
在播放器中使用
import { createPlayer, useSnapshot } from '@videojs/react';
const Player = createPlayer({ /* config */ });
function PlaybackInfo() {
const state = Player.useSnapshot((s) => ({
time: s.currentTime,
duration: s.duration,
buffered: s.buffered,
}));
return (
<div className="info-panel">
<span>{state.time.toFixed(1)}s</span>
<span>/</span>
<span>{state.duration.toFixed(1)}s</span>
</div>
);
}
API 参考
不带选择器
订阅完整的 State 容器状态。
参数
| 参数 | 类型 | 默认值 | 说明 |
|---|
container | StateContainer<S> | — | State 容器实例 |
返回值
S - 完整的 State 类型
带选择器
从 State 容器中选择并订阅派生值。
参数
| 参数 | 类型 | 默认值 | 说明 |
|---|
container | StateContainer<S> | — | State 容器实例 |
selector | (state: S) => R | — | 从状态中选择派生值的函数 |
isEqual | (a: R, b: R) => boolean | shallowEqual | 相等比较函数 |
返回值
R - 选择器返回的派生值类型
State 容器 vs Store
State 容器
StateContainer 是纯状态的容器,只包含状态数据:
interface StateContainer<S> {
subscribe: (callback: () => void) => () => void;
getState: () => S;
}
Store
Store 在 State 容器基础上增加了操作方法:
interface Store<S, A> extends StateContainer<S> {
dispatch: (action: A) => void;
// ... 操作方法如 play(), pause(), seek()
}
选择建议
| 场景 | 推荐 | 说明 |
|---|
| 只需读取状态 | useSnapshot | 更轻量,无需操作方法 |
| 需要执行操作 | useStore | 包含状态和操作 |
| 构建自定义钩子 | useSelector | 最底层,最灵活 |
与 useStore 的关系
useStore 在 useSnapshot 的基础上增加了存储实例的返回:
// useSnapshot - 只返回状态
const state = useSnapshot(container, selector);
// useStore - 同时返回状态和存储实例
const { state, store } = useStore(storeInstance, selector);
当只需要订阅状态而不需要直接调用存储方法时,useSnapshot 是更好的选择。
与 useSelector 的关系
useSnapshot 是 useSelector 的高级封装,专门为 State 容器设计:
// useSnapshot 内部实现
function useSnapshot<S, R>(
container: StateContainer<S>,
selector?: (state: S) => R,
isEqual: Comparator<R> = shallowEqual
) {
return useSelector(
container.subscribe,
() => container.getState(),
selector,
isEqual
);
}
主要区别
| 特性 | useSelector | useSnapshot |
|---|
| 通用性 | 通用(任何订阅源) | 专用(State 容器) |
| 默认比较器 | Object.is | shallowEqual |
| API 简洁性 | 需要提供 subscribe 和 getSnapshot | 直接使用容器 |
使用建议
- 对于 State 容器,使用
useSnapshot 更简洁
- 对于自定义订阅源,使用
useSelector
- 对于 Store,使用
useStore 以获得操作方法
相等性比较
默认行为
useSnapshot 默认使用 shallowEqual 进行浅相等比较:
// 默认浅相等比较
const derived = useSnapshot(container, (state) => ({
time: state.currentTime,
label: state.label,
}));
自定义比较
可以提供自定义的相等函数:
// 使用深度相等
const deep = useSnapshot(
container,
(state) => state.data,
deepEqual // 自定义深度比较
);
// 使用严格相等
const exact = useSnapshot(
container,
(state) => state.id,
Object.is // 严格相等(默认行为)
);
使用场景
派生计算状态
function ComputedState() {
const derived = useSnapshot(container, (state) => ({
// 计算进度百分比
progress: state.duration > 0
? (state.currentTime / state.duration) * 100
: 0,
// 计算剩余时间
remaining: state.duration - state.currentTime,
// 判断是否在播放
isPlaying: !state.paused && state.duration > 0,
}));
return (
<div>
<div className="progress-bar" style={{ width: `${derived.progress}%` }} />
<span>{derived.remaining.toFixed(1)}s remaining</span>
<span>{derived.isPlaying ? 'Playing' : 'Stopped'}</span>
</div>
);
}
条件渲染
function ConditionalRender() {
const isReady = useSnapshot(
container,
(state) => state.readyState >= 3
);
if (!isReady) {
return <Spinner />;
}
return <VideoPlayer />;
}
参见