Skip to content

Commit

Permalink
feat(react): useThrottle 훅 추가 (#474)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssi02014 authored Sep 20, 2024
1 parent 81f69c2 commit 894bf95
Show file tree
Hide file tree
Showing 14 changed files with 318 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/loud-taxis-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-kit/react': minor
---

feat(react): useThrottle 훅 추가 - @ssi02014
8 changes: 4 additions & 4 deletions docs/docs/react/hooks/useDebounce.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const Example = () => {
setCount(count + 1);
};

const countUpWithDebounce = useDebounce(() => {
const debouncedCountUp = useDebounce(() => {
setDebouncedCount(debouncedCount + 1);
}, 1000);

Expand All @@ -55,7 +55,7 @@ const Example = () => {
<div style={{ display: "flex" }}>
<button onClick={countUp}>버튼 클릭</button>
<div style={{ width: "50px" }} />
<button onClick={countUpWithDebounce}>debounce 버튼 클릭</button>
<button onClick={debouncedCountUp}>debounce 버튼 클릭</button>
</div>
<div>
<p>count: {count}</p>
Expand All @@ -74,15 +74,15 @@ export const Example = () => {
const countUp = () => {
setCount(count + 1);
};
const countUpWithDebounce = useDebounce(() => {
const debouncedCountUp = useDebounce(() => {
setDebouncedCount(debouncedCount + 1);
}, 1000);
return (
<div>
<div style={{ display: "flex" }}>
<button onClick={countUp}>버튼 클릭</button>
<div style={{ width: "50px" }} />
<button onClick={countUpWithDebounce}>debounce 버튼 클릭</button>
<button onClick={debouncedCountUp}>debounce 버튼 클릭</button>
</div>
<div>
<p>count: {count}</p>
Expand Down
8 changes: 4 additions & 4 deletions docs/docs/react/hooks/usePreservedCallback.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# usePreservedCallback

주어진 콜백 함수를 보존하고 컴포넌트 렌더링 사이에 재사용할 수 있도록 도와주는 커스텀 훅입니다.
주어진 콜백 함수의 `참조를 유지`하고, 컴포넌트 렌더링 사이에 재사용할 수 있도록 도와주는 커스텀 훅입니다.

이 훅은 특히 콜백 함수가 렌더링 중에 변경될 때 유용합니다. 불필요한 함수 생성을 방지하고 최적화하며, 최신 버전의 콜백 함수를 사용 할 수 있습니다.

Expand All @@ -11,9 +11,9 @@

## Interface
```ts title="typescript"
const usePreservedCallback: <T extends (...args: any[]) => any>(
callback: T
) => (...args: any[]) => any;
function usePreservedCallback<T extends (...args: any[]) => any>(
callback?: T
): T;
```

## Usage
Expand Down
14 changes: 8 additions & 6 deletions docs/docs/react/hooks/usePreservedState.mdx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# usePreservedState

`comparator`로 비교했을 때 값이 변경되었을 때 상태를 변경하는 커스텀 훅입니다.
주어진 ```비교 함수(comparator)`를 사용하여 상태를 보존하는 커스텀 훅입니다.

comparator를 넘기지 않는다면 `@modern-kit/utils``isEqual` 함수가 활용됩니다.
comparator를 넘기지 않는다면 `@modern-kit/utils``isEqual` 함수가 기본 함수로 활용됩니다.
- [isEqual](https://modern-agile-team.github.io/modern-kit/docs/utils/validator/isEqual)

만약, comparator의 반환 값이 `true`여서 상태가 변경되지 않는다면 `참조도 유지`됩니다.
이 훅은 주어진 값이 변경될 때마다 비교 함수(comparator)를 통해 이전 상태와 새로운 값을 비교하여, 값이 다르다고 판단될 때에만 상태를 업데이트합니다.

이를 통해 불필요한 상태 업데이트를 방지할 수 있습니다.

<br />

Expand All @@ -14,10 +16,10 @@ comparator를 넘기지 않는다면 `@modern-kit/utils`의 `isEqual` 함수가

## Interface
```ts title="typescript"
const usePreservedState: <T>(
function usePreservedState<T>(
value: T,
comparator?: ((source: T, target: T) => boolean) | undefined
) => T;
comparator?: (source: T, target: T) => boolean
): T;
```

## Usage
Expand Down
89 changes: 89 additions & 0 deletions docs/docs/react/hooks/useThrottle.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useState } from 'react';
import { useThrottle } from '@modern-kit/react'

# useThrottle

`debounce`를 쉽게 사용할 수 있는 커스텀 훅입니다.

<br />

## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/react/src/hooks/useThrottle/index.ts)

## Interface
```ts title="typescript"
interface ThrottleSettings {
leading?: boolean | undefined;
trailing?: boolean | undefined;
}

type ThrottleParameters = Parameters<typeof throttle>;
```
```ts title="typescript"
function useThrottle<T extends (...args: any) => any>(
callback: T,
wait: ThrottleParameters[1],
options?: ThrottleParameters[2]
): ThrottleReturnType<T>;
```

## Usage
```tsx title="typescript"
import { useState } from 'react';
import { useThrottle } from '@modern-kit/react';

const Example = () => {
const [count, setCount] = useState(1);
const [throttledCount, setThrottledCount] = useState(1);

const countUp = () => {
setCount(count + 1);
};

const throttledCountUp = useThrottle(() => {
setThrottledCount(throttledCount + 1);
}, 1000);

return (
<div>
<div style={{ display: "flex" }}>
<button onClick={countUp}>버튼 클릭</button>
<div style={{ width: "50px" }} />
<button onClick={throttledCountUp}>debounce 버튼 클릭</button>
</div>
<div>
<p>count: {count}</p>
<p>throttledCount: {throttledCount}</p>
</div>
</div>
);
};
```

## Example

export const Example = () => {
const [count, setCount] = useState(1);
const [throttledCount, setThrottledCount] = useState(1);
const countUp = () => {
setCount(count + 1);
};
const throttledCountUp = useThrottle(() => {
setThrottledCount(throttledCount + 1);
}, 1000);
return (
<div>
<div style={{ display: "flex" }}>
<button onClick={countUp}>버튼 클릭</button>
<div style={{ width: "50px" }} />
<button onClick={throttledCountUp}>debounce 버튼 클릭</button>
</div>
<div>
<p>count: {count}</p>
<p>throttledCount: {throttledCount}</p>
</div>
</div>
);
};

<Example />
2 changes: 1 addition & 1 deletion docs/docs/react/hooks/useToggle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ boolean 타입의 state를 Toggle로 쉽게 사용할 수 있는 커스텀 훅

## Interface
```ts title="typescript"
const useToggle: (defaultValue?: boolean) => readonly [boolean, () => void]
function useToggle(defaultValue?: boolean): [boolean, () => void]
```

## Usage
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export * from './useScrollTo';
export * from './useSessionStorage';
export * from './useStep';
export * from './useStepState';
export * from './useThrottle';
export * from './useTimeout';
export * from './useToggle';
export * from './useUnmount';
Expand Down
19 changes: 17 additions & 2 deletions packages/react/src/hooks/usePreservedCallback/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import { noop } from '@modern-kit/utils';
import { useCallback, useRef } from 'react';

/**
* @description 주어진 콜백 함수의 `참조를 유지`하고, 컴포넌트 렌더링 사이에 재사용할 수 있도록 도와주는 커스텀 훅입니다.
*
* 이 훅은 특히 콜백 함수가 렌더링 중에 변경될 때 유용합니다. 불필요한 함수 생성을 방지하고 최적화하며, 최신 버전의 콜백 함수를 사용 할 수 있습니다.
*
* @template T - 콜백 함수의 타입.
* @param {T | undefined} [callback=noop] - 유지하고자 하는 콜백 함수.
* @returns {T} 최신 콜백 함수 참조를 사용하는 메모이제이션된 콜백 함수.
*
* @example
* const preservedCallback = usePreservedCallback(callback);
*
* preservedCallback();
*/
export function usePreservedCallback<T extends (...args: any[]) => any>(
callback: T
) {
callback: T = noop as T
): T {
const callbackRef = useRef<T>(callback);

callbackRef.current = callback;
Expand Down
27 changes: 25 additions & 2 deletions packages/react/src/hooks/usePreservedState/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,33 @@ import { useEffect, useState } from 'react';
import { usePreservedCallback } from '../usePreservedCallback';
import { isEqual } from '@modern-kit/utils';

/**
* @description `usePreservedState`는 주어진 값과 비교 함수(comparator)를 사용하여 상태를 보존하는 커스텀 훅입니다.
*
* 이 훅은 주어진 값이 변경될 때마다 비교 함수(comparator)를 통해 이전 상태와 새로운 값을 비교하여,
* 값이 다르다고 판단될 때에만 상태를 업데이트합니다. 이를 통해 불필요한 상태 업데이트를 방지할 수 있습니다.
*
* @template T - 상태로 보존할 값의 타입.
* @param {T} value - 보존하고자 하는 초기 값.
* @param {(source: T, target: T) => boolean} [comparator=isEqual] - 상태 비교를 위한 함수.
* @returns {T} 보존된 상태 값을 반환합니다.
*
* @example
* // default comparator
* const preservedState = usePreservedState({
* group: 'bgzt',
* });
*
* @example
* // custom comparator
* const preservedState = usePreservedState({
* group: 'bgzt',
* }, (a, b) => a.group === b.group);
*/
export function usePreservedState<T>(
value: T,
comparator: (source: any, target: any) => boolean = isEqual
) {
comparator: (source: T, target: T) => boolean = isEqual
): T {
const [preservedState, setPreservedState] = useState(value);
const callbackComparator = usePreservedCallback(comparator);

Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/hooks/useResizeObserver/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { noop } from '@modern-kit/utils';
import { usePreservedCallback } from '../usePreservedCallback';
import { useCallback, useEffect, useRef, useState } from 'react';

Expand All @@ -18,7 +17,7 @@ export function useResizeObserver<T extends HTMLElement>(
y: 0,
});
const ref = useRef<T>(null);
const callbackAction = usePreservedCallback(action ?? noop);
const callbackAction = usePreservedCallback(action);

const observerCallback = useCallback(
([entry]: ResizeObserverEntry[]) => {
Expand Down
42 changes: 42 additions & 0 deletions packages/react/src/hooks/useThrottle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useMemo } from 'react';
import { throttle } from 'lodash-es';
import { useUnmount } from '../useUnmount';
import { usePreservedState } from '../usePreservedState';
import { usePreservedCallback } from '../usePreservedCallback';

export type ThrottleParameters = Parameters<typeof throttle>;
export type ThrottleReturnType<T extends ThrottleParameters[0]> = ReturnType<
typeof throttle<T>
>;

/**
* @description 주어진 콜백 함수를 지정된 시간 동안 쓰로틀링 처리하여 특정 시간 동안 반복 호출을 방지하는 훅입니다.
*
* @param {ThrottleParameters[0]} callback - 쓰로틀링할 콜백 함수입니다.
* @param {ThrottleParameters[1]} wait - 쓰로틀이 적용될 시간(ms)입니다.
* @param {ThrottleParameters[2]} [options={}] - 쓰로틀 동작에 영향을 주는 추가 옵션입니다. `leading(default: true)`, `trailing(default: true)`, `maxWait` 옵션을 받을 수 있습니다.
*
* @returns {ThrottleReturnType<T>} 쓰로틀링 된 함수가 반환됩니다.
*
* @example
* const throttledFunction = useThrottle(callback, 300);
*
* throttledFunction(); // 쓰로틀링 된 콜백이 실행됩니다.
*/
export function useThrottle<T extends ThrottleParameters[0]>(
callback: T,
wait: ThrottleParameters[1],
options: ThrottleParameters[2] = {}
): ThrottleReturnType<T> {
const callbackAction = usePreservedCallback(callback);
const preservedOptions = usePreservedState(options);

const throttled = useMemo(() => {
return throttle(callbackAction, wait, preservedOptions);
}, [callbackAction, wait, preservedOptions]);

// 언마운트 시 쓰로틀 된 함수의 보류 중인 호출을 모두 버립니다.
useUnmount(() => throttled.cancel());

return throttled;
}
Loading

0 comments on commit 894bf95

Please sign in to comment.