From e150f81fd23ebe98bee70f78559f0de7c08f72b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gromit=20=28=EC=A0=84=EB=AF=BC=EC=9E=AC=29?= <64779472+ssi02014@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:45:24 +0900 Subject: [PATCH] =?UTF-8?q?refac(react):=20storage=20setState=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=B3=80=EA=B2=BD=20(#469)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/young-rats-lick.md | 5 ++++ docs/docs/react/hooks/useLocalStorage.mdx | 4 +-- docs/docs/react/hooks/useSessionStorage.mdx | 4 +-- .../react/src/hooks/useDocumentTitle/index.ts | 17 ++++++++++- .../react/src/hooks/useForceUpdate/index.ts | 11 ++++++- .../react/src/hooks/useLocalStorage/index.ts | 14 ++++++--- .../src/hooks/useOnClickOutside/index.ts | 30 +++++++++++++++++-- .../src/hooks/useSessionStorage/index.ts | 14 ++++++--- packages/utils/src/device/isMobile/index.ts | 17 ++++++++++- 9 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 .changeset/young-rats-lick.md diff --git a/.changeset/young-rats-lick.md b/.changeset/young-rats-lick.md new file mode 100644 index 00000000..1688bdd3 --- /dev/null +++ b/.changeset/young-rats-lick.md @@ -0,0 +1,5 @@ +--- +'@modern-kit/react': patch +--- + +refac(react): storage setState 타입 변경 - @ssi02014 diff --git a/docs/docs/react/hooks/useLocalStorage.mdx b/docs/docs/react/hooks/useLocalStorage.mdx index f6f63f1c..f823cdfe 100644 --- a/docs/docs/react/hooks/useLocalStorage.mdx +++ b/docs/docs/react/hooks/useLocalStorage.mdx @@ -72,7 +72,7 @@ function useLocalStorage({ initialValue, }: UseLocalStorageWithInitialValueProps): { state: T; - setState: (value: T | ((state: T) => T)) => void; + setState: Dispatch>; removeState: () => void; }; @@ -80,7 +80,7 @@ function useLocalStorage({ key, }: UseLocalStorageWithoutInitialValueProps): { state: T | null; - setState: (value: T | ((state: T | null) => T)) => void; + setState: Dispatch>; removeState: () => void; }; ``` diff --git a/docs/docs/react/hooks/useSessionStorage.mdx b/docs/docs/react/hooks/useSessionStorage.mdx index 7c6e10c6..8e175704 100644 --- a/docs/docs/react/hooks/useSessionStorage.mdx +++ b/docs/docs/react/hooks/useSessionStorage.mdx @@ -72,7 +72,7 @@ function useSessionStorage({ initialValue, }: UseSessionStorageWithInitialValueProps): { state: T; - setState: (value: T | ((state: T) => T)) => void; + setState: Dispatch>; removeState: () => void; }; @@ -80,7 +80,7 @@ function useSessionStorage({ key, }: UseSessionStorageWithoutInitialValueProps): { state: T | null; - setState: (value: T | ((state: T | null) => T)) => void; + setState: Dispatch>; removeState: () => void; }; ``` diff --git a/packages/react/src/hooks/useDocumentTitle/index.ts b/packages/react/src/hooks/useDocumentTitle/index.ts index 1ce90975..19e1d6a5 100644 --- a/packages/react/src/hooks/useDocumentTitle/index.ts +++ b/packages/react/src/hooks/useDocumentTitle/index.ts @@ -4,10 +4,25 @@ interface UseDocumentTitleOption { preserveTitleOnUnmount?: boolean; } +/** + * @description Client 환경에서 문서의 제목(`document.title`)을 설정하는 커스텀 훅입니다. + * + * `useDocumentTitle` 훅은 컴포넌트가 마운트될 때 주어진 제목으로 `document.title`을 설정하고, + * 컴포넌트가 언마운트될 때 선택적으로 이전 제목으로 복원할 수 있습니다. + * + * @param {string} title - 설정할 문서의 제목입니다. + * @param {{ preserveTitleOnUnmount: boolean }} options - 옵션 객체입니다. + * - `preserveTitleOnUnmount`: `true`로 설정하면 컴포넌트가 언마운트될 때 제목을 이전 상태로 복원하지 않습니다. 기본값은 `false`입니다. + * + * @returns {void} + * + * @example + * useDocumentTitle('새로운 제목', { preserveTitleOnUnmount: true }); + */ export function useDocumentTitle( title: string, { preserveTitleOnUnmount = false }: UseDocumentTitleOption = {} -) { +): void { useIsomorphicLayoutEffect(() => { const prevTitle = document.title; document.title = title; diff --git a/packages/react/src/hooks/useForceUpdate/index.ts b/packages/react/src/hooks/useForceUpdate/index.ts index ace98668..b1b188f0 100644 --- a/packages/react/src/hooks/useForceUpdate/index.ts +++ b/packages/react/src/hooks/useForceUpdate/index.ts @@ -2,7 +2,16 @@ import { useReducer } from 'react'; const updateReducer = (num: number) => (num + 1) % 10000000; -export function useForceUpdate() { +/** + * @description 컴포넌트의 리렌더링을 강제하는 커스텀 훅입니다. + * 이 훅은 `forceUpdate` 함수를 제공하며, 이 함수를 호출하면 컴포넌트를 강제로 리렌더링합니다. + * + * @returns {() => void} - 호출 시 컴포넌트를 강제로 리렌더링하는 함수입니다. + * + * @example + * const forceUpdate = useForceUpdate(); + */ +export function useForceUpdate(): () => void { const [, forceUpdate] = useReducer(updateReducer, 0); return forceUpdate; diff --git a/packages/react/src/hooks/useLocalStorage/index.ts b/packages/react/src/hooks/useLocalStorage/index.ts index fd71a0e7..042659ca 100644 --- a/packages/react/src/hooks/useLocalStorage/index.ts +++ b/packages/react/src/hooks/useLocalStorage/index.ts @@ -1,5 +1,11 @@ import { isFunction, parseJSON } from '@modern-kit/utils'; -import { useCallback, useMemo, useSyncExternalStore } from 'react'; +import { + Dispatch, + SetStateAction, + useCallback, + useMemo, + useSyncExternalStore, +} from 'react'; import { usePreservedState } from '../usePreservedState'; import { getServerSnapshot, @@ -27,7 +33,7 @@ export function useLocalStorage({ initialValue, }: UseLocalStorageWithInitialValueProps): { state: T; - setState: (value: T | ((state: T) => T)) => void; + setState: Dispatch>; removeState: () => void; }; @@ -35,7 +41,7 @@ export function useLocalStorage({ key, }: UseLocalStorageWithoutInitialValueProps): { state: T | null; - setState: (value: T | ((state: T | null) => T)) => void; + setState: Dispatch>; removeState: () => void; }; @@ -60,7 +66,7 @@ export function useLocalStorage(props: UseLocalStorageProps) { }, [externalStoreState, initialValueToUse]); const setState = useCallback( - (value: T | ((state: T | null) => T)) => { + (value: Dispatch>) => { try { const prevStateString = getSnapshot(key); const prevState = prevStateString diff --git a/packages/react/src/hooks/useOnClickOutside/index.ts b/packages/react/src/hooks/useOnClickOutside/index.ts index f5c9d068..fd3b5595 100644 --- a/packages/react/src/hooks/useOnClickOutside/index.ts +++ b/packages/react/src/hooks/useOnClickOutside/index.ts @@ -2,11 +2,35 @@ import { useEffect, useRef } from 'react'; import { usePreservedCallback } from '../usePreservedCallback'; import { isMobile } from '@modern-kit/utils'; +/** + * @description 특정 요소 외부에서 마우스 또는 터치 이벤트가 발생할 때 호출되는 콜백을 등록하는 커스텀 훅입니다. + * + * `useOnClickOutside` 훅은 지정된 요소(ref로 지정된 요소) 외부에서 사용자가 마우스를 클릭하거나 + * 터치 이벤트가 발생할 때마다 제공된 `callback` 함수를 호출합니다. 모바일 환경에서는 `touchstart` 이벤트를, + * 데스크탑 환경에서는 `mousedown` 이벤트를 감지합니다. + * + * @template T - HTML 요소의 타입. 기본적으로 `HTMLElement`를 상속합니다. + * @param {(targetElement: T) => void} callback - 요소 외부에서 포인터 다운 이벤트가 발생할 때 호출되는 콜백 함수입니다. + * 해당 요소의 레퍼런스를 매개변수로 받습니다. + * + * @returns {{ ref: React.RefObject }} - 외부 클릭 감지를 원하는 DOM 요소에 연결할 ref 객체를 반환합니다. + * + * @example + * ```tsx + * const targetRef = useOnClickOutside((targetElement) => { + * console.log('외부 클릭 감지:', targetElement); + * }); + * + *
+ *
+ *
+ * ``` + */ export function useOnClickOutside( - action: (targetElement: T) => void -) { + callback: (targetElement: T) => void +): { ref: React.RefObject } { const ref = useRef(null); - const callbackAction = usePreservedCallback(action); + const callbackAction = usePreservedCallback(callback); useEffect(() => { const eventType = isMobile() ? 'touchstart' : 'mousedown'; diff --git a/packages/react/src/hooks/useSessionStorage/index.ts b/packages/react/src/hooks/useSessionStorage/index.ts index 483a6af2..64761d1f 100644 --- a/packages/react/src/hooks/useSessionStorage/index.ts +++ b/packages/react/src/hooks/useSessionStorage/index.ts @@ -1,5 +1,11 @@ import { isFunction, parseJSON } from '@modern-kit/utils'; -import { useCallback, useMemo, useSyncExternalStore } from 'react'; +import { + Dispatch, + SetStateAction, + useCallback, + useMemo, + useSyncExternalStore, +} from 'react'; import { usePreservedState } from '../usePreservedState'; import { getServerSnapshot, @@ -26,7 +32,7 @@ export function useSessionStorage({ initialValue, }: UseSessionStorageWithInitialValueProps): { state: T; - setState: (value: T | ((state: T) => T)) => void; + setState: Dispatch>; removeState: () => void; }; @@ -34,7 +40,7 @@ export function useSessionStorage({ key, }: UseSessionStorageWithoutInitialValueProps): { state: T | null; - setState: (value: T | ((state: T | null) => T)) => void; + setState: Dispatch>; removeState: () => void; }; @@ -59,7 +65,7 @@ export function useSessionStorage(props: UseSessionStorageProps) { }, [externalStoreState, initialValueToUse]); const setState = useCallback( - (value: T | ((state: T | null) => T)) => { + (value: Dispatch>) => { try { const prevStateString = getSnapshot(key); const prevState = prevStateString diff --git a/packages/utils/src/device/isMobile/index.ts b/packages/utils/src/device/isMobile/index.ts index cb3c9e43..abef72f3 100644 --- a/packages/utils/src/device/isMobile/index.ts +++ b/packages/utils/src/device/isMobile/index.ts @@ -1,6 +1,21 @@ import { isServer } from '../../device/isServer'; -export function isMobile() { +/** + * @description 현재 장치가 모바일 장치인지 여부를 확인하는 함수입니다. + * + * `isMobile` 함수는 브라우저의 `userAgent` 문자열을 사용하여 현재 사용자가 + * 모바일 장치를 사용 중인지 판별합니다. 만약 서버 환경에서 호출될 경우, 항상 `false`를 반환합니다. + * + * @returns {boolean} - 모바일 장치라면 `true`, 그렇지 않다면 `false`를 반환합니다. + * + * @example + * if (isMobile()) { + * console.log('모바일 장치입니다.'); + * } else { + * console.log('모바일 장치가 아닙니다.'); + * } + */ +export function isMobile(): boolean { if (isServer()) return false; const userAgent = window.navigator.userAgent;