useQuery
这个 Hook 自 v1.5.0 版本起可用。
一个基础的 React 钩子,用于数据获取,支持缓存、自动刷新以及许多其他强大功能。
演示
源码Immediate + Trigger by user + Dependencies
Lifecycle + Refresh + Params + Mutate + Cancel
Throttle + Debounce
Data with debounce: Not loaded
Data with throttle: Not loaded
ReFocus + ReConnect + AutoRefresh + Loading Slow
Error Retry + Cache (SWR)
用法
基础用法
组件挂载时,自动触发数据获取,同时返回数据、加载状态和错误信息,由 useAsyncFn 提供支持。
// fetchData 是请求库无关的,可以是任意返回 Promise 的异步函数,可由 fetch、axios、graphql 等实现
const { loading, data, error } = useQuery(fetchData)
// 加载中、错误 UI 处理
if (loading) return <Loading />
if (error) return <Error />
// 渲染数据
return <div>{data}</div>
手动触发
当设置 manual
为 true
时,所有内置的自动行为(默认都没开启,包括轮询加载、聚焦重加载、网路重连重加载)将失效。同时 immediate
的默认值被指定为 false
。
immediate
由 useMount 提供支持。
const { run, loading, data, error, params } = useQuery(fetchData, {
manual: true,
initialParams: ['params'],
initialData: 'initialData',
})
// 手动触发,会触发 params 更新
run('newParams')
依赖刷新
使用 refreshDependencies
可以设置刷新操作的依赖项,当依赖项改变时,将触发刷新 (refresh()
) 操作,由 useUpdateEffect 提供支持。
const { loading, data, error } = useQuery(fetchData, {
refreshDependencies: [dep1, dep2],
})
操作取消
使用 cancel
可以取消当前请求,同时重置所有状态。请注意,cancel
无法阻止 Promise 执行,只是终止了后续状态更新逻辑,由 useAsyncFn 提供支持
const { cancel, loading, data, error } = useQuery(fetchData)
// 取消请求
cancel()
生命周期
useQuery
提供了丰富的生命周期,由 useAsyncFn 提供支持,包括:
const { loading, data, error } = useQuery(fetchData, {
// 操作执行前
onBefore: (data, params) => console.log('before', data, params),
// 操作执行后
onSuccess: (data, params) => console.log('success', data, params),
// 操作失败
onError: (error, params) => console.log('error', error, params),
// 操作完成
onFinally: (data, params) => console.log('finally', data, error, params),
// 操作取消
onCancel: (data, params) => console.log('cancel', data, error, params),
// 手动修改数据或参数
onMutate: (data, params) => console.log('mutate', data, params),
// 刷新操作
onRefresh: (data, params) => console.log('refresh', data, params),
})
慢加载状态
使用 loadingSlow
可以获取当前操作是否处于慢加载状态,即操作时间超过预期阈值,通过这个状态条件渲染不同的 UI,可以改善用户体验,由 useLoadingSlowFn 提供支持。
const { loading, loadingSlow, data, error } = useQuery(fetchData, {
loadingTimeout: 3_000, // 默认为 0,这里设置为 3 秒
onLoadingSlow: () => console.log('loading slow'), // 慢加载时的回调
})
// 慢加载时显示慢加载 UI
if (loading) return <Loading slow={loadingSlow} />
初始化和刷新中
使用 initializing
和 refreshing
可以获取当前是否处于初始化和刷新中。
这两个状态其实是 loading
和 data
的衍生状态,以下是伪代码:
const initializing = Boolean(!data && loading)
const refreshing = Boolean(data && loading)
const { initializing, refreshing, data, error } = useQuery(fetchData)
// 显示初始化中 UI
if (initializing) return <Loading />;
// 显示已有数据,同时根据 refreshing 状态来改变 UI 与其他样式
return <div>{data}{refreshing && <Loading />}</div>
参数和刷新
使用 params
可以获取上次请求的参数,使用 refresh
可以使用上次请求的参数重新请求,由 useAsyncFn 提供支持。
const { run, params, refresh } = useQuery(fetchData)
// 获取上次请求的参数,存在 initialParams 时第一次为 initialParams,否则第一次为 []
console.log(params)
// 使用上一次的参数重新请求,即「刷新」,等价于 `run(params)`
refresh()
自动清空与取消
使用 clearBeforeRun
可以在每次请求前清空数据,使用 cancelOnUnmount
可以在组件卸载时自动取消请求逻辑(但无法阻止 Promise 执行),由 useAsyncFn 提供支持。
const { loading, data, error } = useQuery(fetchData, {
clearBeforeRun: true, // 每次请求前清空数据,默认 false
cancelOnUnmount: false, // 组件卸载时不取消请求,默认 true
})
防抖和节流
使用 throttle
和 debounce
选项可以控制手动触发的频率,由 useThrottledFn 和 useDebouncedFn 提供支持。
const { run, loading, data, error } = useQuery(fetchData, {
throttle: 1_000, // 在频繁触发的情况下,限制每秒只触发一次
// throttle: { wait: 1_000 }, // 或者指定整个 UseThrottledFnOptions 对象
debounce: 1_000, // 在频繁触发的情况下,等待操作停止的 1 秒后触发
// debounce: { wait: 1_000 }, // 或者指定整个 UseDebouncedFnOptions 对象
})
数据和参数修改
使用 mutate
可以直接修改数据和请求参数,同时触发重新渲染,但不触发额外的请求,由 useAsyncFn 提供支持。
const { data, mutate } = useQuery(fetchData)
// 更新数据
mutate('newData')
// mutate((preData) => 'newData') // 支持 setState 风格的更新
// 同时更新数据和参数
mutate('newData', ['newPrams'])
// 参数也支持 setState 风格的更新
// mutate((preData) => 'newData', (preParams) => ['newPrams'])
// 修改全局缓存
import { mutate } from '@shined/react-use'
mutate((key) => key === 'cacheKey', 'newData')
轮询加载
设置 refreshInterval
为一个大于 0 的数字,将启用自动刷新功能,每隔指定时间重新获取数据,由 useIntervalFn 提供支持。
const { loading, data, error } = useQuery(fetchData, {
refreshInterval: 5_000, // 轮询时间间隔,默认 0,关闭
refreshWhenHidden: true, // 是否在页面不可见时轮询,默认 false
refreshWhenOffline: true, // 是否在离线时轮询,默认 false
})
重聚焦和重连重加载
设置 refreshOnFocus
和 refreshOnReconnect
为 true
,将在页面聚焦和网络重连时重新获取数据,由 useReFocusFn 和 useReConnectFn 提供支持。
const { loading, data, error } = useQuery(fetchData, {
refreshOnFocus: true,
refreshOnReconnect: true,
refreshOnFocusThrottleWait: 3_000, // 聚焦刷新的节流时间,默认 5_000,仅 refreshOnFocus 为 true 时生效
})
如果需要在 React Native、Ink 等非 Web 环境中使用,可以手动指定相关的可见性和网络状况的逻辑。
import { createReactNativeReFocusRegister } from '@shined/react-use'
import { AppState } from 'react-native'
const { loading, data, error } = useQuery(fetchData, {
isVisible: () => true, // 自定义可见性判断函数
isOnline: () => true, // 自定义是否在线判断函数
registerReConnect: createReactNativeReFocusRegister(AppState), // 内置了 React Native 的网络重连注册函数
registerReFocus: (callback) => {}, // 自定义聚焦事件的注册函数
})
可暂停 (Pasuable)
当不指定 manual
为 false
时,如果也想控制内部自动行为,可以使用 Hooks 里对外暴露的 Pausable 实例属性,由 usePausable 提供支持。
const { pause, resume, isActive } = useQuery(fetchData, {
refreshInterval: 5_000, // 指定每 5 秒刷新一次
})
// 暂停自动行为
pause()
// 恢复自动行为
resume()
// 判断是否处于活跃状态
console.log(isActive())
错误重试
设置 errorRetryCount
和 errorRetryInterval
为大于 0 的数字,将在请求失败时自动重试,由 useRetryFn 提供支持。
const { loading, data, error } = useQuery(fetchData, {
errorRetryCount: 3, // 错误重试次数,默认 0,关闭
errorRetryInterval: 1_000, // 错误重试间隔,默认 0,立即重试
onErrorRetry: (error) => console.log(error), // 错误重试时的回调
onErrorRetryFailed: (error) => console.log(error), // 错误重试失败时的回调
})
缓存与 SWR
设置 cacheKey
可以启用缓存功能,缓存的内容包括 data 与 param,当存在缓存数据且未过期时(过期会被清空),会优先返回缓存数据,同时触发请求,更新缓存数据,以保证数据的最新性,也就是 SWR(Stale-While-Revalidate)策略。
const { loading, data, error } = useQuery(fetchData, {
cacheKey: 'cacheKey', // 缓存键,可以是字符串或返回字符串的函数
cacheExpirationTime: 5 * 60 * 1000, // 最大缓存时间,默认 5 分钟,设置 `false` 以禁用
})
可以指定 provider
为一个外部存储(如 Reactive)、localStorage
等,以实现多处共享缓存或者更加精细化的局部缓存。一个 Provider
需要符合以下接口定义(基本就是 Map
类型的接口):
export interface UseQueryCacheLike<Data> {
get(key: string): Data | undefined
set(key: string, value: Data): void
delete(key: string): void
keys(): IterableIterator<string>
}
比如某些部分使用独立的 Map
缓存来共享数据:
const cache = new Map<string, any>()
// 组件 A
const { loading, data, error } = useQuery(fetchData, {
cacheKey: 'cacheKeyA',
provider: cache, // 使用独立的 Map 作为缓存提供者
})
// 组件 B
const { loading, data, error } = useQuery(fetchData, {
cacheKey: 'cacheKeyB',
provider: cache, // 使用独立的 Map 作为缓存提供者
})
// 组件 C, 虽然 cacheKey 与 A 一样,但是 provider 不一样,所以不会共享缓存
const { loading, data, error } = useQuery(fetchData, {
cacheKey: 'cacheKeyA', // 不指定 provider,使用默认的全局共享 Map
})
或者使用 localStorage
作为缓存提供者,以实现页面刷新后的数据持久化。
const localStorageProvider = {
get: (key: string) => {
const value = localStorage.getItem(key)
return value ? JSON.parse(value) : undefined
},
set: (key: string, value: string) => localStorage.setItem(key, JSON.stringify(value)),
delete: (key: string) => localStorage.removeItem(key),
keys: () => Object.keys(localStorage)[Symbol.iterator](),
}
const { loading, data, error } = useQuery(fetchData, {
cacheKey: 'cacheKey',
provider: localStorageProvider, // 使用 localStorage 作为缓存提供者
})
如果 cacheKey
与 provider
均相同,则其会被认为是同一个缓存。同一缓存的任一处状态、数据、请求变更,都会同步到其他处。
这种情况常见于多个组件使用同一份数据,比如用户信息,可能在 nav 组件、header 组件、sidebar 组件等多处使用,这时候可以使用 cacheKey
和 provider
来实现数据的共享。
// 组件 A
const { loading, data, mutate, refresh } = useQuery(fetchData, {
cacheKey: 'cacheKey',
})
// 组件 B
const { data, params } = useQuery(fetchData, {
cacheKey: 'cacheKey',
})
// 在组件 A 中执行 refresh 或者 mutate 操作
refresh()
mutate('newData', ['newParams'])
// 在组件 B 里,data 和 params 会同步更新
console.log(data, params) // 'newData', ['newParams']
自定义数据更新
使用 compare
可以自定义数据更新逻辑,防止频繁刷新,这在处理某些伪不同的数据的情况下非常有用,比如时间戳,可以降低不必要的渲染,默认使用 shallowEqual
,即只比较一层,由 useAsyncFn 提供支持。
const { loading, data, error } = useQuery(fetchData, {
compare: (preData, nextData) => {
// 比较数据是否相同,返回 true 则不更新,返回 false 则更新
return preData === nextData
// 或者比较数据的某个字段,比如 timestamp 可能不同,但实际有效数据是一样的
return deepCompare(preData?.data, nextData?.data)
// 但请注意,数据量太大时,深比较会影响性能,请权衡使用
},
})
依赖收集
useQuery
实现了依赖收集策略,实现了按需渲染,最大限度优化性能,由内部的 useTrackedRefState 提供支持。
// 所有状态变化都会触发重新渲染,不推荐!!!
const fn = useQuery(fetchData)
// 当 loading、data、error 状态变化时,都会触发重新渲染
const { loading, data, error } = useQuery(fetchData)
// 仅当 loading 状态变化时,才会触发重新渲染,data 和 error 状态变化不会触发
const { loading } = useQuery(fetchData)
// 所有支持依赖收集的状态属性
const { loading, data, error, params, loadingSlow, initializing, refreshing } = useQuery(fetchData)
更多详情参考 依赖收集。
源码
API
const {
run, data, loading, refreshing, initializing, error,
cancel, refresh, mutate, loadingSlow, ...pausable
} = useQuery(fetcher, options)
数据获取函数 Fetcher
一个异步函数,用于数据获取,返回一个 Promise,与请求库无关。例如以下都是有效的 Fetcher 函数:
// 原生 Fetch 请求
const fetchData = async () => await (await fetch('https://api.example.com/data').json())
// Axios 请求
const fetchData = () => axios.get('https://api.example.com/data').then(res => res.data)
// GraphQL 请求
const fetchData = () => graphqlClient.query({ query: gql`{ data }` }).then(res => res.data)
// 自定义 Promise 函数
const fetchData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000))
// 可以抛出错误
const fetchData = () => new Promise((_, reject) => setTimeout(() => reject('error'), 1000))
选项 Options
有关更多详情,请查看 UseLoadingSlowFnOptions, UseReConnectOptions, UseReFocusOptions, UseThrottledFnOptions, UseDebouncedFnOptions, UseIntervalFnInterval and UseRetryFnOptions。
export interface UseQueryOptions<T extends AnyFunc, D = Awaited<ReturnType<T>>, E = any>
extends Omit<UseLoadingSlowFnOptions<T, D, E>, 'initialValue'>,
Pick<UseReConnectOptions, 'registerReConnect'>,
Pick<UseReFocusOptions, 'registerReFocus'> {
/**
* 禁用所有自动刷新行为, 默认关闭
*
* @defaultValue false
*/
manual?: boolean
/**
* 初始挂载时传递给提取器的数据
*
* @defaultValue undefined
*/
initialData?: D | undefined
/**
* 缓存键,可以是字符串或返回字符串的函数
*
* @defaultValue undefined
*/
cacheKey?: string | ((...args: Parameters<T> | []) => string)
/**
* 最大缓存时间,指定时间后清除缓存
*
* 默认为 5 分钟,设置 `false` 以禁用
*/
cacheExpirationTime?: number | false
/**
* 缓存提供者,可以设置为外部存储(响应式)、localStorage 等。
*
* 需要符合 CacheLike 接口定义,默认为全局共享的 `new Map()`
*
* @defaultValue global shared `new Map()`
*/
provider?: Gettable<CacheLike<D>>
/**
* 节流选项 => 仅影响手动执行 run 方法的频率
*
* @defaultValue undefined
*/
throttle?: UseThrottledFnOptions['wait'] | UseThrottledFnOptions
/**
* 防抖选项 => 仅影响手动执行 run 方法的频率
*
* @defaultValue undefined
*/
debounce?: UseDebouncedFnOptions['wait'] | UseDebouncedFnOptions
/**
* 获取焦点时是否重新加载, 默认关闭
*
* @defaultValue false
*/
refreshOnFocus?: boolean
/**
* 获取焦点时的节流时间,默认 5_000 (毫秒),只有在 refreshOnFocus 为 true 时生效
*
* @defaultValue 5_000
*/
refreshOnFocusThrottleWait?: number
/**
* 自定义可见性判断函数
*
* @defaultValue defaultIsVisible
*/
isVisible?: () => Promisable<boolean>
/**
* 网络重连时是否重新加载, 默认关闭
*
* @defaultValue false
*/
refreshOnReconnect?: boolean
/**
* 自定义在线判断函数
*
* @defaultValue defaultIsOnline
*/
isOnline?: () => Promisable<boolean>
/**
* 自动刷新的间隔时间,默认为 0,关闭
*
* @defaultValue 0
*/
refreshInterval?: Exclude<UseIntervalFnInterval, 'requestAnimationFrame'>
/**
* 隐藏时是否重新加载,默认关闭
*
* @defaultValue false
*/
refreshWhenHidden?: boolean
/**
* 离线时是否重新加载,默认关闭
*
* @defaultValue false
*/
refreshWhenOffline?: boolean
/**
* 刷新操作的依赖项,当依赖项改变时,将触发刷新操作
*
* @defaultValue []
*/
refreshDependencies?: DependencyList
/**
* 错误重试次数
*
* @defaultValue 0
*/
errorRetryCount?: UseRetryFnOptions<E>['count']
/**
* 错误重试间隔
*
* @defaultValue 0
*/
errorRetryInterval?: UseRetryFnOptions<E>['interval']
/**
* 是否在每次请求前清除缓存
*
* @defaultValue undefined
*/
onErrorRetry?: UseRetryFnOptions<E>['onErrorRetry']
/**
* 错误重试失败时的回调
*
* @defaultValue undefined
*/
onErrorRetryFailed?: UseRetryFnOptions<E>['onRetryFailed']
}
返回值
返回值中包含可暂停、恢复的 Pausable 实例。
更多详情,请参见 Pausable。
有关更多详情,请查看 UseLoadingSlowFnReturns。
export interface UseQueryReturns<T extends AnyFunc, D = Awaited<ReturnType<T>>, E = any>
extends Pausable,
Omit<UseLoadingSlowFnReturns<T, D, E>, 'value'> {
/**
* 请求返回的数据
*/
data: D | undefined
/**
* 请求是否处于初始化状态(无数据 + 加载中, initializing => !data && loading)
*/
initializing: boolean
/**
* 请求是否正在刷新数据(有数据 + 加载中, refreshing => data && loading)
*/
refreshing: boolean
}