) => void` |
+
### Options
-| 参数 | 说明 | 类型 | 默认值 |
-| ------------ | ------------------ | -------------------------- | ----------------------------- |
-| defaultValue | 默认值 | `any \| (() => any)` | - |
-| serializer | 自定义序列化方法 | `(value: any) => string` | `JSON.stringify` |
-| deserializer | 自定义反序列化方法 | `(value: string) => any` | `JSON.parse` |
-| onError | 错误回调函数 | `(error: unknown) => void` | `(e) => { console.error(e) }` |
+| 参数 | 说明 | 类型 | 默认值 |
+| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ----------------------------- |
+| defaultValue | 默认值 | `any \| (() => any)` | - |
+| listenStorageChange | 是否监听存储变化。如果是 `true`,当存储值变化时,所有 `key` 相同的 `useLocalStorageState` 会同步状态,包括同一浏览器不同 tab 之间 | `boolean` | `false` |
+| serializer | 自定义序列化方法 | `(value: any) => string` | `JSON.stringify` |
+| deserializer | 自定义反序列化方法 | `(value: string) => any` | `JSON.parse` |
+| onError | 错误回调函数 | `(error: unknown) => void` | `(e) => { console.error(e) }` |
## 备注
diff --git a/packages/hooks/src/useLockFn/index.en-US.md b/packages/hooks/src/useLockFn/index.en-US.md
index c570cc4d72..2275287de3 100644
--- a/packages/hooks/src/useLockFn/index.en-US.md
+++ b/packages/hooks/src/useLockFn/index.en-US.md
@@ -16,7 +16,7 @@ Add lock to an async function to prevent parallel executions.
## API
```typescript
-function useLockFn(
+function useLockFn
(
fn: (...args: P) => Promise
): fn: (...args: P) => Promise;
```
diff --git a/packages/hooks/src/useLockFn/index.ts b/packages/hooks/src/useLockFn/index.ts
index 5ce96fba40..5569a65a1a 100644
--- a/packages/hooks/src/useLockFn/index.ts
+++ b/packages/hooks/src/useLockFn/index.ts
@@ -1,6 +1,6 @@
import { useRef, useCallback } from 'react';
-function useLockFn(fn: (...args: P) => Promise) {
+function useLockFn(fn: (...args: P) => Promise) {
const lockRef = useRef(false);
return useCallback(
@@ -9,11 +9,11 @@ function useLockFn(fn: (...args: P
lockRef.current = true;
try {
const ret = await fn(...args);
- lockRef.current = false;
return ret;
} catch (e) {
- lockRef.current = false;
throw e;
+ } finally {
+ lockRef.current = false;
}
},
[fn],
diff --git a/packages/hooks/src/useLockFn/index.zh-CN.md b/packages/hooks/src/useLockFn/index.zh-CN.md
index 61ace21a64..60c48f2759 100644
--- a/packages/hooks/src/useLockFn/index.zh-CN.md
+++ b/packages/hooks/src/useLockFn/index.zh-CN.md
@@ -16,7 +16,7 @@ nav:
## API
```typescript
-function useLockFn
(
+function useLockFn
(
fn: (...args: P) => Promise
): fn: (...args: P) => Promise;
```
diff --git a/packages/hooks/src/useLongPress/index.en-US.md b/packages/hooks/src/useLongPress/index.en-US.md
index ea1163581f..fc7e6aa2a3 100644
--- a/packages/hooks/src/useLongPress/index.en-US.md
+++ b/packages/hooks/src/useLongPress/index.en-US.md
@@ -27,7 +27,7 @@ Listen for the long press event of the target element.
useLongPress(
onLongPress: (event: MouseEvent | TouchEvent) => void,
target: Target,
- options?: {
+ options: {
delay?: number;
moveThreshold?: { x?: number; y?: number };
onClick?: (event: MouseEvent | TouchEvent) => void;
@@ -42,7 +42,7 @@ useLongPress(
| ----------- | ---------------------------- | ----------------------------------------------------------- | ------- |
| onLongPress | Trigger function | `(event: MouseEvent \| TouchEvent) => void` | - |
| target | DOM node or Ref | `Element` \| `() => Element` \| `MutableRefObject` | - |
-| options | Optional configuration items | `Options` | - |
+| options | Optional configuration items | `Options` | `{}` |
### Options
diff --git a/packages/hooks/src/useLongPress/index.ts b/packages/hooks/src/useLongPress/index.ts
index df3f8be751..a676bcef90 100644
--- a/packages/hooks/src/useLongPress/index.ts
+++ b/packages/hooks/src/useLongPress/index.ts
@@ -87,7 +87,7 @@ function useLongPress(
const onMove = (event: TouchEvent) => {
if (timerRef.current && overThreshold(event)) {
- clearInterval(timerRef.current);
+ clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
diff --git a/packages/hooks/src/useLongPress/index.zh-CN.md b/packages/hooks/src/useLongPress/index.zh-CN.md
index d1cedd91c3..a3683ca2cf 100644
--- a/packages/hooks/src/useLongPress/index.zh-CN.md
+++ b/packages/hooks/src/useLongPress/index.zh-CN.md
@@ -27,7 +27,7 @@ nav:
useLongPress(
onLongPress: (event: MouseEvent | TouchEvent) => void,
target: Target,
- options?: {
+ options: {
delay?: number;
moveThreshold?: { x?: number; y?: number };
onClick?: (event: MouseEvent | TouchEvent) => void;
@@ -42,7 +42,7 @@ useLongPress(
| ----------- | ---------------- | ----------------------------------------------------------- | ------ |
| onLongPress | 触发函数 | `(event: MouseEvent \| TouchEvent) => void` | - |
| target | DOM 节点或者 Ref | `Element` \| `() => Element` \| `MutableRefObject` | - |
-| options | 可选配置项 | `Options` | - |
+| options | 可选配置项 | `Options` | `{}` |
### Options
diff --git a/packages/hooks/src/useMemoizedFn/index.en-US.md b/packages/hooks/src/useMemoizedFn/index.en-US.md
index 4a42b52724..291837f115 100644
--- a/packages/hooks/src/useMemoizedFn/index.en-US.md
+++ b/packages/hooks/src/useMemoizedFn/index.en-US.md
@@ -5,7 +5,7 @@ nav:
# useMemoizedFn
-Hooks for persistent functions. In theory, useMemoizedFn can be used instead of useCallback.
+Hooks for persistent functions. In general, useMemoizedFn can be used instead of useCallback. See [FAQ](#faq) for special cases.
In some scenarios, we need to use useCallback to cache a function, but when the second parameter deps changes, the function will be regenerated, causing the function reference to change.
@@ -42,17 +42,25 @@ const func = useMemoizedFn(() => {
## API
```typescript
-const fn = useMemoizedFn(fn: T): T;
+const memoizedFn = useMemoizedFn(fn: T): T;
```
### Result
-| Property | Description | Type |
-| -------- | -------------------------------------- | ------------------------- |
-| fn | Fn the reference address never changes | `(...args: any[]) => any` |
+| Property | Description | Type |
+| ---------- | ------------------------------------------------- | ------------------------- |
+| memoizedFn | Function that the reference address never changes | `(...args: any[]) => any` |
### Params
| Property | Description | Type | Default |
| -------- | --------------------------------- | ------------------------- | ------- |
| fn | Function that require persistence | `(...args: any[]) => any` | - |
+
+## FAQ
+
+### The function returned by `useMemoizedFn` will not inherit properties from fn itself?
+
+The function returned by `useMemoizedFn` is entirely different from the reference of the passed `fn`, and it does not inherit any properties from `fn` itself. If you want to preserve the properties of the function itself after memoization, `useMemoizedFn` currently does not fulfill that requirement. In this case, consider downgrading to using `useCallback` or `useMemo` instead.
+
+Related issues: [2273](https://github.com/alibaba/hooks/issues/2273)
diff --git a/packages/hooks/src/useMemoizedFn/index.ts b/packages/hooks/src/useMemoizedFn/index.ts
index fdd2d3cbc9..b297013f55 100644
--- a/packages/hooks/src/useMemoizedFn/index.ts
+++ b/packages/hooks/src/useMemoizedFn/index.ts
@@ -20,7 +20,7 @@ function useMemoizedFn(fn: T) {
// why not write `fnRef.current = fn`?
// https://github.com/alibaba/hooks/issues/728
- fnRef.current = useMemo(() => fn, [fn]);
+ fnRef.current = useMemo(() => fn, [fn]);
const memoizedFn = useRef>();
if (!memoizedFn.current) {
diff --git a/packages/hooks/src/useMemoizedFn/index.zh-CN.md b/packages/hooks/src/useMemoizedFn/index.zh-CN.md
index 0bf7c8bed0..a3017c9846 100644
--- a/packages/hooks/src/useMemoizedFn/index.zh-CN.md
+++ b/packages/hooks/src/useMemoizedFn/index.zh-CN.md
@@ -5,7 +5,7 @@ nav:
# useMemoizedFn
-持久化 function 的 Hook,理论上,可以使用 useMemoizedFn 完全代替 useCallback。
+持久化 function 的 Hook,一般情况下,可以使用 useMemoizedFn 完全代替 useCallback,特殊情况见 [FAQ](#faq)。
在某些场景中,我们需要使用 useCallback 来记住一个函数,但是在第二个参数 deps 变化时,会重新生成函数,导致函数地址变化。
@@ -42,17 +42,25 @@ const func = useMemoizedFn(() => {
## API
```typescript
-const fn = useMemoizedFn(fn: T): T;
+const memoizedFn = useMemoizedFn(fn: T): T;
```
### Result
-| 参数 | 说明 | 类型 |
-| ---- | ------------------------- | ------------------------- |
-| fn | 引用地址永远不会变化的 fn | `(...args: any[]) => any` |
+| 参数 | 说明 | 类型 |
+| ---------- | -------------------------- | ------------------------- |
+| memoizedFn | 引用地址永远不会变化的函数 | `(...args: any[]) => any` |
### Params
| 参数 | 说明 | 类型 | 默认值 |
| ---- | ---------------- | ------------------------- | ------ |
| fn | 需要持久化的函数 | `(...args: any[]) => any` | - |
+
+## FAQ
+
+### `useMemoizedFn` 返回的函数没有继承 fn 自身的属性?
+
+`useMemoizedFn` 返回的函数与传入的 fn 的引用完全不同,且没有继承 fn 自身的属性。如果想要持久化后函数自身的属性不丢失,目前 `useMemoizedFn` 满足不了,请降级使用 `useCallback`、`useMemo`。
+
+Related issues: [2273](https://github.com/alibaba/hooks/issues/2273)
diff --git a/packages/hooks/src/useRafInterval/index.ts b/packages/hooks/src/useRafInterval/index.ts
index ecdab92cfa..15ad94226c 100644
--- a/packages/hooks/src/useRafInterval/index.ts
+++ b/packages/hooks/src/useRafInterval/index.ts
@@ -3,7 +3,7 @@ import useLatest from '../useLatest';
import { isNumber } from '../utils';
interface Handle {
- id: number | NodeJS.Timer;
+ id: number | ReturnType;
}
const setRafInterval = function (callback: () => void, delay: number = 0): Handle {
@@ -12,15 +12,15 @@ const setRafInterval = function (callback: () => void, delay: number = 0): Handl
id: setInterval(callback, delay),
};
}
- let start = new Date().getTime();
+ let start = Date.now();
const handle: Handle = {
id: 0,
};
const loop = () => {
- const current = new Date().getTime();
+ const current = Date.now();
if (current - start >= delay) {
callback();
- start = new Date().getTime();
+ start = Date.now();
}
handle.id = requestAnimationFrame(loop);
};
@@ -28,7 +28,7 @@ const setRafInterval = function (callback: () => void, delay: number = 0): Handl
return handle;
};
-function cancelAnimationFrameIsNotDefined(t: any): t is NodeJS.Timer {
+function cancelAnimationFrameIsNotDefined(t: any): t is ReturnType {
return typeof cancelAnimationFrame === typeof undefined;
}
@@ -51,27 +51,25 @@ function useRafInterval(
const fnRef = useLatest(fn);
const timerRef = useRef();
+ const clear = useCallback(() => {
+ if (timerRef.current) {
+ clearRafInterval(timerRef.current);
+ }
+ }, []);
+
useEffect(() => {
- if (!isNumber(delay) || delay < 0) return;
+ if (!isNumber(delay) || delay < 0) {
+ return;
+ }
if (immediate) {
fnRef.current();
}
timerRef.current = setRafInterval(() => {
fnRef.current();
}, delay);
- return () => {
- if (timerRef.current) {
- clearRafInterval(timerRef.current);
- }
- };
+ return clear;
}, [delay]);
- const clear = useCallback(() => {
- if (timerRef.current) {
- clearRafInterval(timerRef.current);
- }
- }, []);
-
return clear;
}
diff --git a/packages/hooks/src/useRafTimeout/index.ts b/packages/hooks/src/useRafTimeout/index.ts
index a0a5cd3af4..e9b6ed12eb 100644
--- a/packages/hooks/src/useRafTimeout/index.ts
+++ b/packages/hooks/src/useRafTimeout/index.ts
@@ -46,24 +46,20 @@ function useRafTimeout(fn: () => void, delay: number | undefined) {
const fnRef = useLatest(fn);
const timerRef = useRef();
+ const clear = useCallback(() => {
+ if (timerRef.current) {
+ clearRafTimeout(timerRef.current);
+ }
+ }, []);
+
useEffect(() => {
if (!isNumber(delay) || delay < 0) return;
timerRef.current = setRafTimeout(() => {
fnRef.current();
}, delay);
- return () => {
- if (timerRef.current) {
- clearRafTimeout(timerRef.current);
- }
- };
+ return clear;
}, [delay]);
- const clear = useCallback(() => {
- if (timerRef.current) {
- clearRafTimeout(timerRef.current);
- }
- }, []);
-
return clear;
}
diff --git a/packages/hooks/src/useReactive/__tests__/index.test.tsx b/packages/hooks/src/useReactive/__tests__/index.test.tsx
index c261658709..954d3b71ec 100644
--- a/packages/hooks/src/useReactive/__tests__/index.test.tsx
+++ b/packages/hooks/src/useReactive/__tests__/index.test.tsx
@@ -195,6 +195,36 @@ describe('test useReactive feature', () => {
expect(() => result.current.v.Module).not.toThrowError();
});
+ it('test JSX element', () => {
+ const hook = renderHook(() => useReactive({ html: foo
}));
+ const proxy = hook.result.current;
+ const wrap = render(proxy.html);
+ const html = wrap.getByRole('id');
+
+ expect(html.textContent).toBe('foo');
+ act(() => {
+ proxy.html = bar
;
+ wrap.rerender(proxy.html);
+ });
+ expect(html.textContent).toBe('bar');
+ hook.unmount();
+ });
+
+ it('test read-only and non-configurable data property', () => {
+ const obj = {} as { user: { name: string } };
+ Reflect.defineProperty(obj, 'user', {
+ value: { name: 'foo' },
+ writable: false,
+ configurable: false,
+ });
+
+ const hook = renderHook(() => useReactive(obj));
+ const proxy = hook.result.current;
+
+ expect(() => proxy.user.name).not.toThrowError();
+ hook.unmount();
+ });
+
it('test input1', () => {
const wrap = render();
diff --git a/packages/hooks/src/useReactive/index.ts b/packages/hooks/src/useReactive/index.ts
index 1543e9b3aa..48b04fda2b 100644
--- a/packages/hooks/src/useReactive/index.ts
+++ b/packages/hooks/src/useReactive/index.ts
@@ -1,5 +1,5 @@
import { useRef } from 'react';
-import { isPlainObject } from 'lodash-es';
+import isPlainObject from 'lodash/isPlainObject';
import useCreation from '../useCreation';
import useUpdate from '../useUpdate';
@@ -26,6 +26,12 @@ function observer>(initialVal: T, cb: () => void):
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
+ // https://github.com/alibaba/hooks/issues/1317
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
+ if (!descriptor?.configurable && !descriptor?.writable) {
+ return res;
+ }
+
// Only proxy plain object or array,
// otherwise it will cause: https://github.com/alibaba/hooks/issues/2080
return isPlainObject(res) || Array.isArray(res) ? observer(res, cb) : res;
diff --git a/packages/hooks/src/useRequest/__tests__/useLoadingDelayPlugin.test.ts b/packages/hooks/src/useRequest/__tests__/useLoadingDelayPlugin.test.ts
index 35765f90da..cd58c81e25 100644
--- a/packages/hooks/src/useRequest/__tests__/useLoadingDelayPlugin.test.ts
+++ b/packages/hooks/src/useRequest/__tests__/useLoadingDelayPlugin.test.ts
@@ -1,4 +1,5 @@
import { act, renderHook, waitFor } from '@testing-library/react';
+import type { RenderHookResult } from '@testing-library/react';
import useRequest from '../index';
import { request } from '../../utils/testingHelpers';
@@ -6,8 +7,12 @@ describe('useLoadingDelayPlugin', () => {
jest.useFakeTimers();
const setUp = (service, options) => renderHook((o) => useRequest(service, o || options));
+ let hook: RenderHookResult, any>;
+
+ afterEach(() => {
+ hook.unmount();
+ });
- let hook;
it('useLoadingDelayPlugin should work', async () => {
act(() => {
hook = setUp(request, {
@@ -53,7 +58,7 @@ describe('useLoadingDelayPlugin', () => {
jest.advanceTimersByTime(3000);
});
- expect(hook.result.current.loading).toBe(false);
+ await waitFor(() => expect(hook.result.current.loading).toBe(false));
});
it('useLoadingDelayPlugin should update loading when ready is undefined', async () => {
@@ -68,6 +73,6 @@ describe('useLoadingDelayPlugin', () => {
jest.advanceTimersByTime(3000);
});
- expect(hook.result.current.loading).toBe(true);
+ await waitFor(() => expect(hook.result.current.loading).toBe(true));
});
});
diff --git a/packages/hooks/src/useRequest/doc/cache/cache.en-US.md b/packages/hooks/src/useRequest/doc/cache/cache.en-US.md
index d521283d6f..58dce1b057 100644
--- a/packages/hooks/src/useRequest/doc/cache/cache.en-US.md
+++ b/packages/hooks/src/useRequest/doc/cache/cache.en-US.md
@@ -29,10 +29,12 @@ By setting `staleTime`, we can specify the data retention time, during which tim
### Data sharing
-The content of the same `cacheKey` is shared globally, which will bring the following features
+> Note: If no new request is issued, the "Data sharing" will not be triggered. `cacheTime` and `staleTime` parameters will invalidate "Data sharing". [#2313](https://github.com/alibaba/hooks/issues/2313)
-- Sharing request `Promise`, only one of the same `cacheKey` will initiate a request at the same time, and the subsequent ones will share the same request `Promise`.
-- Data synchronization. At any time, when we change the content of one of the `cacheKey`, the content of the other `cacheKey` will be synchronized.
+The content of the same `cacheKey` is shared globally, which will bring the following features:
+
+- Sharing request `Promise`: Only one of the same `cacheKey` will initiate a request at the same time, and the subsequent ones will share the same request `Promise`.
+- Data synchronization: When a request is made by one `cacheKey`, the contents of other identical `cacheKey` will be synchronized accordingly.
In the following example, the two components will only initiate one request during initialization. And the content of the two articles is always synchronized.
@@ -77,7 +79,7 @@ interface CachedData {
| Property | Description | Type | Default |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -------- |
-| cacheKey | A unique ID of the request. If `cacheKey` is set, we will enable the caching mechanism. The data of the same `cacheKey` is globally synchronized. | `string` | - |
+| cacheKey | A unique ID of the request. Data of the same `cacheKey` will synchronized globally (`cacheTime` and `staleTime` parameters will invalidate this mechanism, see demo: [Data sharing](#data-sharing)) | `string` | - |
| cacheTime | - Set the cache time. By default, the cached data will be cleared after 5 minutes.
- If set to `-1`, the cached data will never expire
| `number` | `300000` |
| staleTime | - Time to consider the cached data is fresh. Within this time interval, the request will not be re-initiated
- If set to `-1`, it means that the data is always fresh
| `number` | `0` |
| setCache | - Custom set cache
- `setCache` and `getCache` need to be used together
- In the custom cache mode, `cacheTime` and `clearCache` are useless, please implement it yourself according to the actual situation.
| `(data: CachedData) => void;` | - |
diff --git a/packages/hooks/src/useRequest/doc/cache/cache.zh-CN.md b/packages/hooks/src/useRequest/doc/cache/cache.zh-CN.md
index d5bfc547ca..4ec3444abf 100644
--- a/packages/hooks/src/useRequest/doc/cache/cache.zh-CN.md
+++ b/packages/hooks/src/useRequest/doc/cache/cache.zh-CN.md
@@ -29,10 +29,12 @@ group:
### 数据共享
-同一个 `cacheKey` 的内容,在全局是共享的,这会带来以下几个特性
+> 注意:如果没有发起新请求,不会触发数据共享。`cacheTime`、`staleTime` 参数会使数据共享失效。[#2313](https://github.com/alibaba/hooks/issues/2313)
-- 请求 `Promise` 共享,相同的 `cacheKey` 同时只会有一个在发起请求,后发起的会共用同一个请求 `Promise`
-- 数据同步,任何时候,当我们改变其中某个 `cacheKey` 的内容时,其它相同 `cacheKey` 的内容均会同步
+同一个 `cacheKey` 的内容,在全局是共享的,这会带来以下几个特性:
+
+- 请求 `Promise` 共享:相同的 `cacheKey` 同时只会有一个在发起请求,后发起的会共用同一个请求 `Promise`
+- 数据同步:当某个 `cacheKey` 发起请求时,其它相同 `cacheKey` 的内容均会随之同步
下面的示例中,初始化时,两个组件只会发起一个请求。并且两篇文章的内容永远是同步的。
@@ -77,7 +79,7 @@ interface CachedData {
| 参数 | 说明 | 类型 | 默认值 |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -------- |
-| cacheKey | 请求唯一标识。如果设置了 `cacheKey`,我们会启用缓存机制。同一个 `cacheKey` 的数据全局同步。 | `string` | - |
+| cacheKey | 请求的唯一标识。相同 `cacheKey` 的数据全局同步(`cacheTime`、`staleTime` 参数会使该机制失效,见示例:[数据共享](#数据共享))。 | `string` | - |
| cacheTime | - 设置缓存数据回收时间。默认缓存数据 5 分钟后回收
- 如果设置为 `-1`, 则表示缓存数据永不过期
| `number` | `300000` |
| staleTime | - 缓存数据保持新鲜时间。在该时间间隔内,认为数据是新鲜的,不会重新发请求
- 如果设置为 `-1`,则表示数据永远新鲜
| `number` | `0` |
| setCache | - 自定义设置缓存
- `setCache` 和 `getCache` 需要配套使用
- 在自定义缓存模式下,`cacheTime` 和 `clearCache` 不会生效,请根据实际情况自行实现。
| `(data: CachedData) => void;` | - |
diff --git a/packages/hooks/src/useRequest/doc/polling/polling.en-US.md b/packages/hooks/src/useRequest/doc/polling/polling.en-US.md
index 495b46706c..56c69db578 100644
--- a/packages/hooks/src/useRequest/doc/polling/polling.en-US.md
+++ b/packages/hooks/src/useRequest/doc/polling/polling.en-US.md
@@ -58,4 +58,5 @@ You can experience the effect through the following example.
- `options.pollingInterval`, `options.pollingWhenHidden` support dynamic changes.
- If you set `options.manual = true`, the initialization will not start polling, you need start it by `run/runAsync`.
+- If the `pollingInterval` changes from 0 to a value greater than 0, polling will not start automatically, and you need start it by `run/runAsync`.
- The polling logic is to wait for `pollingInterval` time after each request is completed, and then initiate the next request.
diff --git a/packages/hooks/src/useRequest/doc/polling/polling.zh-CN.md b/packages/hooks/src/useRequest/doc/polling/polling.zh-CN.md
index b043adf048..b124f49d02 100644
--- a/packages/hooks/src/useRequest/doc/polling/polling.zh-CN.md
+++ b/packages/hooks/src/useRequest/doc/polling/polling.zh-CN.md
@@ -50,7 +50,7 @@ const { data, run, cancel } = useRequest(getUsername, {
| 参数 | 说明 | 类型 | 默认值 |
| ---------------------- | ------------------------------------------------------------------------------------------------------ | --------- | ------ |
-| pollingInterval | 轮询间隔,单位为毫秒。如果值大于 0,则启动轮询模式。 | `number` | `0` |
+| pollingInterval | 轮询间隔,单位为毫秒。如果值大于 0,则处于轮询模式。 | `number` | `0` |
| pollingWhenHidden | 在页面隐藏时,是否继续轮询。如果设置为 false,在页面隐藏时会暂时停止轮询,页面重新显示时继续上次轮询。 | `boolean` | `true` |
| pollingErrorRetryCount | 轮询错误重试次数。如果设置为 -1,则无限次 | `number` | `-1` |
@@ -58,4 +58,5 @@ const { data, run, cancel } = useRequest(getUsername, {
- `options.pollingInterval`、`options.pollingWhenHidden` 支持动态变化。
- 如果设置 `options.manual = true`,则初始化不会启动轮询,需要通过 `run/runAsync` 触发开始。
+- 如果设置 `pollingInterval` 由 `0` 变成 `大于 0` 的值,不会启动轮询,需要通过 `run/runAsync` 触发开始。
- 轮询原理是在每次请求完成后,等待 `pollingInterval` 时间,发起下一次请求。
diff --git a/packages/hooks/src/useRequest/doc/ready/ready.en-US.md b/packages/hooks/src/useRequest/doc/ready/ready.en-US.md
index 6baac8b28d..0e3d6b0caa 100644
--- a/packages/hooks/src/useRequest/doc/ready/ready.en-US.md
+++ b/packages/hooks/src/useRequest/doc/ready/ready.en-US.md
@@ -7,7 +7,7 @@ group:
# Ready
-useRequest provides an `options.ready`, when its value is `false`, the request will never be sent.
+By setting `options.ready`, you can control whether a request is sent. When its value is `false`, the request will never be sent.
The specific behavior is as follows:
diff --git a/packages/hooks/src/useRequest/doc/ready/ready.zh-CN.md b/packages/hooks/src/useRequest/doc/ready/ready.zh-CN.md
index b2f5c802a9..9b04d6e705 100644
--- a/packages/hooks/src/useRequest/doc/ready/ready.zh-CN.md
+++ b/packages/hooks/src/useRequest/doc/ready/ready.zh-CN.md
@@ -7,7 +7,7 @@ group:
# Ready
-useRequest 提供了一个 `options.ready` 参数,当其值为 `false` 时,请求永远都不会发出。
+通过设置 `options.ready`,可以控制请求是否发出。当其值为 `false` 时,请求永远都不会发出。
其具体行为如下:
diff --git a/packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDeps.tsx b/packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDeps.tsx
index 531407e769..a9cb3020fd 100644
--- a/packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDeps.tsx
+++ b/packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDeps.tsx
@@ -1,45 +1,37 @@
+/**
+ * title: Repeat last request
+ * desc: When the dependency array changes, use the previous parameters to make the request again.
+ *
+ * title.zh-CN: 重复上一次请求
+ * desc.zh-CN: 依赖数组变化时,使用上一次的参数重新发起请求。
+ */
+
import React, { useState } from 'react';
+import Mock from 'mockjs';
+import { Space, Button } from 'antd';
import { useRequest } from 'ahooks';
-const userSchool = (id: string) => {
- switch (id) {
- case '1':
- return 'Tsinghua University';
- case '2':
- return 'Beijing University';
- case '3':
- return 'Zhejiang University';
- default:
- return '';
- }
-};
+function getUsername(id: number): Promise {
+ console.log('getUsername id:', id);
-async function getUserSchool(userId: string): Promise {
return new Promise((resolve) => {
setTimeout(() => {
- resolve(userSchool(userId));
+ resolve(Mock.mock('@name'));
}, 1000);
});
}
export default () => {
- const [userId, setUserId] = useState('1');
- const { data, loading } = useRequest(() => getUserSchool(userId), {
+ const [userId, setUserId] = useState();
+ const { data, loading, run } = useRequest((id: number) => getUsername(id), {
refreshDeps: [userId],
});
return (
-
-
-
School: {loading ? 'Loading' : data}
-
+
+ Username: {loading ? 'loading...' : data}
+
+
+
);
};
diff --git a/packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDepsAction.tsx b/packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDepsAction.tsx
new file mode 100644
index 0000000000..63007e9181
--- /dev/null
+++ b/packages/hooks/src/useRequest/doc/refreshDeps/demo/refreshDepsAction.tsx
@@ -0,0 +1,50 @@
+/**
+ * title: Custom refresh
+ * desc: This example shows that when the dependency array changes, it checks the parameters' validity first and then makes a new request.
+ *
+ * title.zh-CN: 自定义刷新行为
+ * desc.zh-CN: 该示例展示了当依赖数组变化时,首先校验参数合法性,然后发起新的请求。
+ */
+
+import React, { useState } from 'react';
+import Mock from 'mockjs';
+import isNumber from 'lodash/isNumber';
+import { Button, Space } from 'antd';
+import { useRequest } from 'ahooks';
+
+function getUsername(id: number): Promise {
+ console.log('getUsername id:', id);
+
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(Mock.mock('@name'));
+ }, 1000);
+ });
+}
+
+export default () => {
+ const [userId, setUserId] = useState();
+ const { data, loading, run } = useRequest((id: number) => getUsername(id), {
+ refreshDeps: [userId],
+ refreshDepsAction: () => {
+ if (!isNumber(userId)) {
+ console.log(
+ `parameter "userId" expected to be a number, but got ${typeof userId}.`,
+ userId,
+ );
+ return;
+ }
+ run(userId);
+ },
+ });
+
+ return (
+
+ Username: {loading ? 'loading...' : data}
+
+
+
+ );
+};
diff --git a/packages/hooks/src/useRequest/doc/refreshDeps/refresyDeps.en-US.md b/packages/hooks/src/useRequest/doc/refreshDeps/refresyDeps.en-US.md
index 3e4e85e0e3..f1263e2e22 100644
--- a/packages/hooks/src/useRequest/doc/refreshDeps/refresyDeps.en-US.md
+++ b/packages/hooks/src/useRequest/doc/refreshDeps/refresyDeps.en-US.md
@@ -7,11 +7,10 @@ group:
# RefreshDeps
-useRequest provides an `options.refreshDeps`, which will trigger the request refresh when its value changes.
+By setting `options.refreshDeps`, `useRequest` will run [refresh](https://ahooks.js.org/hooks/use-request/basic/#result) automatically when dependencies change, achieving the effect of [Refresh (repeat the last request)](https://ahooks.js.org/hooks/use-request/basic/#refresh-repeat-the-last-request).
```tsx | pure
const [userId, setUserId] = useState('1');
-
const { data, run } = useRequest(() => getUserSchool(userId), {
refreshDeps: [userId],
});
@@ -23,7 +22,6 @@ It is exactly the same with the following implementation
```tsx | pure
const [userId, setUserId] = useState('1');
-
const { data, refresh } = useRequest(() => getUserSchool(userId));
useEffect(() => {
@@ -31,14 +29,23 @@ useEffect(() => {
}, [userId]);
```
-You can experience the effect through the following example
+### Repeat last request
+### Custom refresh
+
+
+
## API
### Options
-| Property | Description | Type | Default |
-| ----------- | ------------------------------------------------------- | ---------------------- | ------- |
-| refreshDeps | When the content of the array changes, trigger refresh. | `React.DependencyList` | `[]` |
+| Property | Description | Type | Default |
+| ----------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------- | ------- |
+| refreshDeps | When the content of the array changes, trigger refresh. | `React.DependencyList` | `[]` |
+| refreshDepsAction | Customize the request behavior during dependency refresh; this parameter is invoked when dependencies change. | `() => void` | - |
+
+## Remark
+
+- If you set `options.manual = true`, both `refreshDeps` and `refreshDepsAction` are no longer effective, you need to trigger the request by `run/runAsync`.
diff --git a/packages/hooks/src/useRequest/doc/refreshDeps/refresyDeps.zh-CN.md b/packages/hooks/src/useRequest/doc/refreshDeps/refresyDeps.zh-CN.md
index 4dfe7c3de9..aff568171a 100644
--- a/packages/hooks/src/useRequest/doc/refreshDeps/refresyDeps.zh-CN.md
+++ b/packages/hooks/src/useRequest/doc/refreshDeps/refresyDeps.zh-CN.md
@@ -7,11 +7,10 @@ group:
# 依赖刷新
-useRequest 提供了一个 `options.refreshDeps` 参数,当它的值变化后,会重新触发请求。
+通过设置 `options.refreshDeps`,在依赖变化时, `useRequest` 会自动调用 [refresh](https://ahooks.js.org/zh-CN/hooks/use-request/basic/#result) 方法,实现[刷新(重复上一次请求)](https://ahooks.js.org/zh-CN/hooks/use-request/basic/#刷新重复上一次请求)的效果。
```tsx | pure
const [userId, setUserId] = useState('1');
-
const { data, run } = useRequest(() => getUserSchool(userId), {
refreshDeps: [userId],
});
@@ -23,7 +22,6 @@ const { data, run } = useRequest(() => getUserSchool(userId), {
```tsx | pure
const [userId, setUserId] = useState('1');
-
const { data, refresh } = useRequest(() => getUserSchool(userId));
useEffect(() => {
@@ -31,14 +29,23 @@ useEffect(() => {
}, [userId]);
```
-你可以通过下面示例来体验效果
+### 重复上一次请求
+### 自定义刷新行为
+
+
+
## API
### Options
-| 参数 | 说明 | 类型 | 默认值 |
-| ----------- | ------------------------------------------------------------------- | ------- | ------ |
-| refreshDeps | 依赖数组,当数组内容变化后,发起请求。同 `useEffect` 的第二个参数。 | `any[]` | `[]` |
+| 参数 | 说明 | 类型 | 默认值 |
+| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------ |
+| refreshDeps | 依赖数组。当数组内容变化后[刷新(重复上一次请求)](https://ahooks.js.org/zh-CN/hooks/use-request/basic/#刷新重复上一次请求)。同 `useEffect` 的第二个参数。 | `any[]` | `[]` |
+| refreshDepsAction | 自定义依赖数组变化时的请求行为。 | `() => void` | - |
+
+## 备注
+
+- 如果设置 `options.manual = true`,则 `refreshDeps`, `refreshDepsAction` 都不再生效,需要通过 `run/runAsync` 手动触发请求。
diff --git a/packages/hooks/src/useRequest/src/plugins/useDebouncePlugin.ts b/packages/hooks/src/useRequest/src/plugins/useDebouncePlugin.ts
index e6e264e029..84c548d287 100644
--- a/packages/hooks/src/useRequest/src/plugins/useDebouncePlugin.ts
+++ b/packages/hooks/src/useRequest/src/plugins/useDebouncePlugin.ts
@@ -1,5 +1,5 @@
-import type { DebouncedFunc, DebounceSettings } from 'lodash-es';
-import { debounce } from 'lodash-es';
+import type { DebouncedFunc, DebounceSettings } from 'lodash';
+import debounce from 'lodash/debounce';
import { useEffect, useMemo, useRef } from 'react';
import type { Plugin } from '../types';
diff --git a/packages/hooks/src/useRequest/src/plugins/useThrottlePlugin.ts b/packages/hooks/src/useRequest/src/plugins/useThrottlePlugin.ts
index 6f2fc2c386..955c331d41 100644
--- a/packages/hooks/src/useRequest/src/plugins/useThrottlePlugin.ts
+++ b/packages/hooks/src/useRequest/src/plugins/useThrottlePlugin.ts
@@ -1,5 +1,5 @@
-import type { DebouncedFunc, ThrottleSettings } from 'lodash-es';
-import { throttle } from 'lodash-es';
+import type { DebouncedFunc, ThrottleSettings } from 'lodash';
+import throttle from 'lodash/throttle';
import { useEffect, useRef } from 'react';
import type { Plugin } from '../types';
diff --git a/packages/hooks/src/useResetState/__tests__/index.test.ts b/packages/hooks/src/useResetState/__tests__/index.test.ts
index 8728302445..b4fa76640b 100644
--- a/packages/hooks/src/useResetState/__tests__/index.test.ts
+++ b/packages/hooks/src/useResetState/__tests__/index.test.ts
@@ -2,9 +2,9 @@ import { act, renderHook } from '@testing-library/react';
import useResetState from '../index';
describe('useResetState', () => {
- const setUp = (initialState: S) =>
+ const setUp = (initialState) =>
renderHook(() => {
- const [state, setState, resetState] = useResetState(initialState);
+ const [state, setState, resetState] = useResetState(initialState);
return {
state,
@@ -20,6 +20,13 @@ describe('useResetState', () => {
expect(hook.result.current.state).toEqual({ hello: 'world' });
});
+ it('should support functional initialValue', () => {
+ const hook = setUp(() => ({
+ hello: 'world',
+ }));
+ expect(hook.result.current.state).toEqual({ hello: 'world' });
+ });
+
it('should reset state', () => {
const hook = setUp({
hello: '',
@@ -49,4 +56,38 @@ describe('useResetState', () => {
});
expect(hook.result.current.state).toEqual({ count: 1 });
});
+
+ it('should keep random initial state', () => {
+ const random = Math.random();
+ const hook = setUp({
+ count: random,
+ });
+
+ act(() => {
+ hook.result.current.setState({ count: Math.random() });
+ });
+
+ act(() => {
+ hook.result.current.resetState();
+ });
+
+ expect(hook.result.current.state).toEqual({ count: random });
+ });
+
+ it('should support random functional initialValue', () => {
+ const random = Math.random();
+ const hook = setUp(() => ({
+ count: random,
+ }));
+
+ act(() => {
+ hook.result.current.setState({ count: Math.random() });
+ });
+
+ act(() => {
+ hook.result.current.resetState();
+ });
+
+ expect(hook.result.current.state).toEqual({ count: random });
+ });
});
diff --git a/packages/hooks/src/useResetState/demo/demo1.tsx b/packages/hooks/src/useResetState/demo/demo1.tsx
index 5a98c7f876..d77bd29ecf 100644
--- a/packages/hooks/src/useResetState/demo/demo1.tsx
+++ b/packages/hooks/src/useResetState/demo/demo1.tsx
@@ -1,33 +1,37 @@
-import React from 'react';
+import React, { useMemo } from 'react';
+import { Button, Space } from 'antd';
import { useResetState } from 'ahooks';
-interface State {
- hello: string;
- count: number;
-}
-
export default () => {
- const [state, setState, resetState] = useResetState({
+ const initialValue = {
hello: '',
- count: 0,
- });
+ value: Math.random(),
+ };
+ const initialValueMemo = useMemo(() => {
+ return initialValue;
+ }, []);
+
+ const [state, setState, resetState] = useResetState(initialValue);
return (
+
initial state:
+
{JSON.stringify(initialValueMemo, null, 2)}
+
current state:
{JSON.stringify(state, null, 2)}
-
-
+ set hello and value
+
+
resetState
+
);
};
diff --git a/packages/hooks/src/useResetState/index.ts b/packages/hooks/src/useResetState/index.ts
index bcff86c842..eaa7d6000e 100644
--- a/packages/hooks/src/useResetState/index.ts
+++ b/packages/hooks/src/useResetState/index.ts
@@ -1,16 +1,25 @@
-import { useState } from 'react';
+import { useRef, useState } from 'react';
import type { Dispatch, SetStateAction } from 'react';
+import { isFunction } from '../utils';
import useMemoizedFn from '../useMemoizedFn';
+import useCreation from '../useCreation';
type ResetState = () => void;
const useResetState = (
initialState: S | (() => S),
): [S, Dispatch>, ResetState] => {
- const [state, setState] = useState(initialState);
+ const initialStateRef = useRef(initialState);
+ const initialStateMemo = useCreation(
+ () =>
+ isFunction(initialStateRef.current) ? initialStateRef.current() : initialStateRef.current,
+ [],
+ );
+
+ const [state, setState] = useState(initialStateMemo);
const resetState = useMemoizedFn(() => {
- setState(initialState);
+ setState(initialStateMemo);
});
return [state, setState, resetState];
diff --git a/packages/hooks/src/useResponsive/__tests__/index.test.ts b/packages/hooks/src/useResponsive/__tests__/index.test.ts
index f3fd4ccc45..0ece26a151 100644
--- a/packages/hooks/src/useResponsive/__tests__/index.test.ts
+++ b/packages/hooks/src/useResponsive/__tests__/index.test.ts
@@ -1,5 +1,5 @@
import { renderHook, act } from '../../utils/tests';
-import { useResponsive } from '../';
+import useResponsive from '../';
describe('useResponsive', () => {
function changeWidth(width: number) {
diff --git a/packages/hooks/src/useResponsive/index.ts b/packages/hooks/src/useResponsive/index.ts
index 406a161ff6..117d8a43af 100644
--- a/packages/hooks/src/useResponsive/index.ts
+++ b/packages/hooks/src/useResponsive/index.ts
@@ -49,7 +49,7 @@ export function configResponsive(config: ResponsiveConfig) {
if (info) calculate();
}
-export function useResponsive() {
+function useResponsive() {
if (isBrowser && !listening) {
info = {};
calculate();
@@ -83,3 +83,5 @@ export function useResponsive() {
return state;
}
+
+export default useResponsive;
diff --git a/packages/hooks/src/useSelections/__tests__/index.test.ts b/packages/hooks/src/useSelections/__tests__/index.test.ts
index ea943f0f90..07088485f0 100644
--- a/packages/hooks/src/useSelections/__tests__/index.test.ts
+++ b/packages/hooks/src/useSelections/__tests__/index.test.ts
@@ -1,125 +1,244 @@
import { act, renderHook } from '@testing-library/react';
+import { useState } from 'react';
import useSelections from '../index';
+import type { Options } from '../index';
-const data = [1, 2, 3];
+const _data = [1, 2, 3];
+const _selected = [1];
+const _selectedItem = 1;
-const setup = (items: T[], defaultSelected?: T[]) => {
- return renderHook(() => useSelections(items, defaultSelected));
+const _dataObj = [{ id: 1 }, { id: 2 }, { id: 3 }];
+const _selectedObj = [{ id: 1 }];
+const _selectedItemObj = { id: 1 };
+
+const setup = (items: T[], options?: T[] | Options) => {
+ return renderHook(() => useSelections(items, options));
+};
+
+interface CaseCallback {
+ (data: T[], selected: T[], selectedItem: T): void;
+}
+
+const runCaseCallback = (
+ dataCallback: CaseCallback,
+ objDataCallback: CaseCallback