useSelector
预计阅读时间: 5 分钟
低级钩子,用于订阅派生状态。
useSelector 是 Video.js React 中的低级订阅钩子,它使用 useSyncExternalStore 来确保在并发模式下安全地订阅外部状态。这个钩子是构建更高级钩子的基础,如 useStore 和 usePlayer。
示例
基本用法
import { useSelector } from '@videojs/react';
import { store } from './myStore';
function SelectedValue() {
const value = useSelector(
store.subscribe,
() => store.getState(),
(state) => state.selectedId
);
return <div>Selected: {value}</div>;
}
自定义相等函数
import { useSelector } from '@videojs/react';
import { store } from './myStore';
function PlayerState() {
// 使用浅相等比较
const { paused, currentTime, duration } = useSelector(
store.subscribe,
() => store.getState(),
(state) => ({
paused: state.paused,
currentTime: state.currentTime,
duration: state.duration,
}),
(a, b) => shallowEqual(a, b) // 自定义相等函数
);
return (
<div>
<p>Paused: {String(paused)}</p>
<p>Time: {currentTime.toFixed(1)}s / {duration.toFixed(1)}s</p>
</div>
);
}
与 useStore 的关系
useStore 实际上是 useSelector 的高级封装:
// useStore 内部实现(简化)
function useStore<S, R>(store: S, selector?: SelectorFn, isEqual?: Comparator) {
return useSelector(
store.subscribe,
() => store.getState(),
selector,
isEqual
);
}
// 直接使用 useSelector
function CustomHook() {
const result = useSelector(
myStore.subscribe,
() => myStore.getState(),
(state) => computeDerivedValue(state),
deepEqual
);
return result;
}
API 参考
参数
| 参数 | 类型 | 默认值 | 说明 |
|---|
subscribe | SubscribeFn | — | 订阅函数,接收回调并在变更时调用 |
getSnapshot | () => T | — | 获取当前快照的函数 |
selector | (state: T) => R | — | 从状态中选择派生值的函数 |
isEqual | (a: R, b: R) => boolean | — | 自定义相等比较函数 |
SubscribeFn
type SubscribeFn = (callback: () => void) => () => void
订阅函数应返回取消订阅的函数。
getSnapshot
type GetSnapshotFn<T> = () => T
返回状态的当前快照。对于状态容器,这通常是 getState()。
selector
type SelectorFn<T, R> = (state: T) => R
从状态中选择派生值。如果不提供,将返回整个状态。
isEqual
type Comparator<R> = (a: R, b: R) => boolean
用于比较新旧值是否相等的函数。如果返回 true,组件不会重新渲染。
返回值
当不提供选择器时,返回类型为 T(状态的完整快照)。
相等性比较
默认比较器
如果不提供 isEqual 参数,默认使用 Object.is 进行严格相等比较:
// 默认严格相等
const value = useSelector(
store.subscribe,
() => store.getState(),
(state) => state.counter // 只有当 counter 引用改变时才重新渲染
);
浅相等比较
Video.js React 提供了 shallowEqual 函数用于浅相等比较:
import { useSelector, shallowEqual } from '@videojs/react';
const selected = useSelector(
store.subscribe,
() => store.getState(),
(state) => ({ id: state.id, label: state.label }),
shallowEqual // 当所有属性的值相等时才重新渲染
);
自定义比较器
可以提供自定义的相等函数:
import { useSelector } from '@videojs/react';
// 深度相等比较
const data = useSelector(
store.subscribe,
() => store.getState(),
(state) => state.data,
deepEqual // 自定义深度比较
);
// 特定字段比较
const hasChanged = useSelector(
store.subscribe,
() => store.getState(),
(state) => state.timestamp,
(a, b) => Math.abs(a - b) < 1000 // 1秒内的变化视为相同
);
与 useStore/useSnapshot 的关系
useSnapshot
useSnapshot 是专门为 State 容器设计的 useSelector 封装:
// useSnapshot 内部使用 useSelector
function useSnapshot(container, selector) {
return useSelector(
container.subscribe,
() => container.getState(),
selector,
shallowEqual
);
}
useStore
useStore 在 useSelector 基础上增加了存储实例的直接访问:
// useStore 同时返回状态和存储实例
function useStore<S, R>(store: S, selector?: SelectorFn, isEqual?: Comparator) {
const state = useSelector(
store.subscribe,
() => store.getState(),
selector,
isEqual
);
// 额外返回存储实例
return { store, state };
}
使用场景
派生计算值
function ProgressBar() {
const progress = useSelector(
store.subscribe,
() => store.getState(),
(state) => state.duration > 0
? (state.currentTime / state.duration) * 100
: 0
);
return <div className="progress" style={{ width: `${progress}%` }} />;
}
订阅多个状态片段
function MediaInfo() {
const { paused, muted, volume } = useSelector(
store.subscribe,
() => store.getState(),
(state) => ({
paused: state.paused,
muted: state.muted,
volume: state.volume,
}),
shallowEqual
);
return (
<div>
{paused ? 'Paused' : 'Playing'}
{muted ? ' (Muted)' : ''}
Volume: {(volume * 100).toFixed(0)}%
</div>
);
}
参见