Skip to content

Commit

Permalink
feat: add useRafTimeout (#1527)
Browse files Browse the repository at this point in the history
* websocket透传ws实例给options里的事件

* 删除demo2

* feat: 用RAF实现useTimeout

Co-authored-by: chj_damon <chjdamon@gmail.com>
  • Loading branch information
chj-damon and chj_damon authored Mar 30, 2022
1 parent 43e2be0 commit 68e11d8
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 1 deletion.
1 change: 1 addition & 0 deletions config/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const menus = [
'useInterval',
'useRafInterval',
'useTimeout',
'useRafTimeout',
'useLockFn',
'useUpdate',
],
Expand Down
2 changes: 2 additions & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import useWebSocket from './useWebSocket';
import useWhyDidYouUpdate from './useWhyDidYouUpdate';
import { createUpdateEffect } from './createUpdateEffect';
import useRafInterval from './useRafInterval';
import useRafTimeout from './useRafTimeout';

export {
useRequest,
Expand Down Expand Up @@ -148,4 +149,5 @@ export {
useFocusWithin,
createUpdateEffect,
useRafInterval,
useRafTimeout,
};
2 changes: 1 addition & 1 deletion packages/hooks/src/useRafInterval/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Please note that the following two cases are likely to be inapplicable, and `use
- the time interval is less than `16ms`
- want to execute the timer when page is not rendering;

> `requestAnimationFrame` will automatically downgrade to `setInterval` in node enviraonment
> `requestAnimationFrame` will automatically downgrade to `setInterval` in node environment
## Examples

Expand Down
39 changes: 39 additions & 0 deletions packages/hooks/src/useRafTimeout/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { renderHook } from '@testing-library/react-hooks';
import useRafTimeout from '../index';

interface ParamsObj {
fn: (...arg: any) => any;
delay: number | undefined;
}

const setUp = ({ fn, delay }: ParamsObj) => renderHook(() => useRafTimeout(fn, delay));

const FRAME_TIME = 16.7;
describe('useRafTimeout', () => {
beforeAll(() => {
jest.useFakeTimers('modern');
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should be defined', () => {
expect(useRafTimeout).toBeDefined();
});

it('timeout should work', () => {
const callback = jest.fn();
setUp({ fn: callback, delay: FRAME_TIME });
expect(callback).not.toBeCalled();
jest.advanceTimersByTime(FRAME_TIME * 2.5);
expect(callback).toHaveBeenCalledTimes(1);
});

it('timeout should stop when delay is undefined', () => {
const delay: number | undefined = undefined;
const callback = jest.fn();
setUp({ fn: callback, delay });
expect(callback).not.toBeCalled();
jest.advanceTimersByTime(FRAME_TIME * 1.5);
expect(callback).not.toBeCalled();
});
});
37 changes: 37 additions & 0 deletions packages/hooks/src/useRafTimeout/__tests__/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { renderHook } from '@testing-library/react-hooks';
import useRafTimeout from '../index';

interface ParamsObj {
fn: (...arg: any) => any;
delay: number | undefined;
}

const setUp = ({ fn, delay }: ParamsObj) => renderHook(() => useRafTimeout(fn, delay));

const FRAME_TIME = 16.7;
describe('useRafTimeout', () => {
beforeAll(() => {
jest.useFakeTimers('modern');
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should downgrade to setTimeout when requstAnimationFrame is undefined', () => {
const _requestAnimationFrame = global.requestAnimationFrame;
const _cancelAnimationFrame = global.cancelAnimationFrame;

// @ts-ignore
delete global.requestAnimationFrame;
// @ts-ignore
delete global.cancelAnimationFrame;

const callback = jest.fn();
setUp({ fn: callback, delay: FRAME_TIME });
expect(callback).not.toBeCalled();
jest.advanceTimersByTime(FRAME_TIME * 1.5);
expect(callback).toHaveBeenCalledTimes(1);

global.requestAnimationFrame = _requestAnimationFrame;
global.cancelAnimationFrame = _cancelAnimationFrame;
});
});
20 changes: 20 additions & 0 deletions packages/hooks/src/useRafTimeout/demo/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* title: Basic usage
* desc: Execute after 2000ms.
*
* title.zh-CN: 基础用法
* desc.zh-CN: 在 2000ms 后执行。
*/

import React, { useState } from 'react';
import { useRafTimeout } from 'ahooks';

export default () => {
const [count, setCount] = useState(0);

useRafTimeout(() => {
setCount(count + 1);
}, 2000);

return <div>count: {count}</div>;
};
44 changes: 44 additions & 0 deletions packages/hooks/src/useRafTimeout/demo/demo2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* title: Advanced usage
* desc: Modify the delay to realize the timer timeout change and pause.
*
* title.zh-CN: 进阶使用
* desc.zh-CN: 动态修改 delay 以实现定时器间隔变化与暂停。
*/

import React, { useState } from 'react';
import { useRafTimeout } from 'ahooks';

export default () => {
const [count, setCount] = useState(0);
const [delay, setDelay] = useState<number | undefined>(1000);

useRafTimeout(() => {
setCount(count + 1);
}, delay);

return (
<div>
<p> count: {count} </p>
<p style={{ marginTop: 16 }}> Delay: {delay} </p>
<button onClick={() => setDelay((t) => (!!t ? t + 1000 : 1000))} style={{ marginRight: 8 }}>
Delay + 1000
</button>
<button
style={{ marginRight: 8 }}
onClick={() => {
setDelay(1000);
}}
>
reset Delay
</button>
<button
onClick={() => {
setDelay(undefined);
}}
>
clear
</button>
</div>
);
};
36 changes: 36 additions & 0 deletions packages/hooks/src/useRafTimeout/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
nav:
path: /hooks
---

# useRafTimeout

A hook implements with `requestAnimationFrame` for better performance. The API is consistent with `useTimeout`.

> `requestAnimationFrame` will automatically downgrade to `setTimeout` in node environment
## Examples

### Default usage

<code src="./demo/demo1.tsx" />

### Advanced usage

<code src="./demo/demo2.tsx" />

## API

```typescript
useRafTimeout(
fn: () => void,
delay?: number | undefined,
);
```

### Params

| Property | Description | Type |
|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
| fn | The function to be executed every `delay` milliseconds. | `() => void` |
| delay | The time in milliseconds, the timer should delay in between executions of the specified function. The timer will be cancelled if delay is set to `undefined`. | `number` \| `undefined` |
59 changes: 59 additions & 0 deletions packages/hooks/src/useRafTimeout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useEffect } from 'react';
import useLatest from '../useLatest';

interface Handle {
id: number | NodeJS.Timeout;
}

const setRafTimeout = function (callback: () => void, delay: number = 16.7): Handle {
if (typeof requestAnimationFrame === typeof undefined) {
return {
id: setTimeout(callback, delay),
};
}
const handle: Handle = {
id: 0,
};

const now = Date.now;
let startTime = now();
let endTime = startTime;

const loop = () => {
handle.id = requestAnimationFrame(loop);
endTime = now();
if (endTime - startTime >= delay) {
callback();
clearRafTimeout(handle);
}
};
handle.id = requestAnimationFrame(loop);
return handle;
};

function cancelAnimationFrameIsNotDefined(t: any): t is NodeJS.Timer {
return typeof cancelAnimationFrame === typeof undefined;
}

const clearRafTimeout = function (handle: Handle) {
if (cancelAnimationFrameIsNotDefined(handle.id)) {
return clearTimeout(handle.id);
}
cancelAnimationFrame(handle.id);
};

function useRafTimeout(fn: () => void, delay: number | undefined) {
const fnRef = useLatest(fn);

useEffect(() => {
if (typeof delay !== 'number' || delay < 0) return;
const timer = setRafTimeout(() => {
fnRef.current();
}, delay);
return () => {
clearRafTimeout(timer);
};
}, [delay]);
}

export default useRafTimeout;
36 changes: 36 additions & 0 deletions packages/hooks/src/useRafTimeout/index.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
nav:
path: /hooks
---

# useRafTimeout

`requestAnimationFrame` 模拟实现 `setTimeout`,API 和 `useTimeout` 保持一致

> Node 环境下 `requestAnimationFrame` 会自动降级到 `setTimeout`
## 代码演示

### 基础用法

<code src="./demo/demo1.tsx" />

### 进阶使用

<code src="./demo/demo2.tsx" />

## API

```typescript
useRafTimeout(
fn: () => void,
delay?: number | undefined,
);
```

### Params

| 参数 | 说明 | 类型 |
|---------|---------------------------------------------|-------------------------|
| fn | 要定时调用的函数 | `() => void` |
| delay | 间隔时间,当取值 `undefined` 时会停止计时器 | `number` \| `undefined` |

0 comments on commit 68e11d8

Please sign in to comment.