;
+
+export default function shallowEqualsObjects(objectA: ObjectValue, objectB: ObjectValue): boolean {
+ // 참조가 같으면 true
+ if (objectA === objectB) {
+ return true;
+ }
+
+ // 둘 중 하나라도 null 또는 undefined면 false
+ if (!objectA || !objectB) {
+ return false;
+ }
+
+ const objectAKeys = Object.keys(objectA);
+ const objectBKeys = Object.keys(objectB);
+ const objectALength = objectAKeys.length;
+ const objectBLength = objectBKeys.length;
+
+ // 키가 개수가 다르면 false
+ if (objectALength !== objectBLength) {
+ return false;
+ }
+
+ for (let i = 0; i < objectALength; i++) {
+ const key = objectAKeys[i];
+
+ // 값이 다르거나, B에 해당 key가 없으면 다름
+ if (objectA[key] !== objectB[key] || !Object.hasOwn(objectB, key)) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/packages/lib/src/hocs/deepMemo.ts b/packages/lib/src/hocs/deepMemo.ts
index 13f7f18e..b234690d 100644
--- a/packages/lib/src/hocs/deepMemo.ts
+++ b/packages/lib/src/hocs/deepMemo.ts
@@ -1,5 +1,7 @@
import type { FunctionComponent } from "react";
+import { memo } from "./memo.ts";
+import { deepEquals } from "../equals";
export function deepMemo(Component: FunctionComponent
) {
- return Component;
+ return memo(Component, deepEquals);
}
diff --git a/packages/lib/src/hocs/index.ts b/packages/lib/src/hocs/index.ts
index 1366564d..f719bcbb 100644
--- a/packages/lib/src/hocs/index.ts
+++ b/packages/lib/src/hocs/index.ts
@@ -1,2 +1,2 @@
export * from "./deepMemo";
-export * from "./memo";
+export * from "./memo.ts";
diff --git a/packages/lib/src/hocs/memo.ts b/packages/lib/src/hocs/memo.ts
index 532c3a5c..2b3f5378 100644
--- a/packages/lib/src/hocs/memo.ts
+++ b/packages/lib/src/hocs/memo.ts
@@ -1,6 +1,27 @@
-import { type FunctionComponent } from "react";
+import type { FunctionComponent, ReactNode } from "react";
import { shallowEquals } from "../equals";
+import { useRef } from "../hooks";
export function memo
(Component: FunctionComponent
, equals = shallowEquals) {
- return Component;
+ return function MemoizedComponent(props: P) {
+ const prevPropsRef = useRef
(null);
+ const prevComponentRef = useRef | null>(null);
+
+ // 첫 렌더링이면, 이전 컴포넌트를 생성하고 반환한다.
+ if (!prevPropsRef.current) {
+ prevPropsRef.current = props;
+ prevComponentRef.current = Component({ ...props });
+ return prevComponentRef.current;
+ }
+
+ // props가 변경되지 않았다면, 이전 컴포넌트를 반환한다.
+ if (equals(prevPropsRef.current, props)) {
+ return prevComponentRef.current;
+ }
+
+ // props가 변경되었으므로, 이전 컴포넌트를 삭제하고 새로운 컴포넌트를 생성한다.
+ prevPropsRef.current = props;
+ prevComponentRef.current = Component({ ...props });
+ return prevComponentRef.current;
+ };
}
diff --git a/packages/lib/src/hooks/useAutoCallback.ts b/packages/lib/src/hooks/useAutoCallback.ts
index 23100162..ea8b0991 100644
--- a/packages/lib/src/hooks/useAutoCallback.ts
+++ b/packages/lib/src/hooks/useAutoCallback.ts
@@ -3,5 +3,10 @@ import { useCallback } from "./useCallback";
import { useRef } from "./useRef";
export const useAutoCallback = (fn: T): T => {
- return fn;
+ const fnRef = useRef(fn);
+ fnRef.current = fn;
+
+ return useCallback((...args: Parameters): ReturnType => {
+ return fnRef.current(...args);
+ }, []) as T;
};
diff --git a/packages/lib/src/hooks/useCallback.ts b/packages/lib/src/hooks/useCallback.ts
index 712bc898..6e7a64e5 100644
--- a/packages/lib/src/hooks/useCallback.ts
+++ b/packages/lib/src/hooks/useCallback.ts
@@ -1,7 +1,7 @@
-/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-unsafe-function-type */
+/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import type { DependencyList } from "react";
+import { useMemo } from "./useMemo.ts";
export function useCallback(factory: T, _deps: DependencyList) {
- // 직접 작성한 useMemo를 통해서 만들어보세요.
- return factory as T;
+ return useMemo(() => factory, _deps);
}
diff --git a/packages/lib/src/hooks/useMemo.ts b/packages/lib/src/hooks/useMemo.ts
index e80692e2..2b32d070 100644
--- a/packages/lib/src/hooks/useMemo.ts
+++ b/packages/lib/src/hooks/useMemo.ts
@@ -1,8 +1,30 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
import type { DependencyList } from "react";
import { shallowEquals } from "../equals";
+import { useRef } from "./useRef.ts";
export function useMemo(factory: () => T, _deps: DependencyList, _equals = shallowEquals): T {
- // 직접 작성한 useRef를 통해서 만들어보세요.
- return factory();
+ // 메모이제이션 된 함수의 결과
+ const memoizedValueRef = useRef(null);
+ // 의존성
+ const depsRef = useRef(null);
+
+ // 초기 실행이거나 의존성이 변경되었을 때만 함수를 재생성한다.
+ if (depsRef.current === null || (_deps.length > 0 && !_equals(depsRef.current, _deps))) {
+ try {
+ // factory 함수 실행 결과를 캐싱한다.
+ memoizedValueRef.current = factory();
+ // 의존성을 업데이트한다.
+ depsRef.current = _deps;
+ } catch (error) {
+ console.error("메모이제이션 실패:", error);
+ }
+ }
+
+ // 메모이제이션 된 함수가 null이면 예외를 발생시킨다.
+ if (memoizedValueRef.current === null) {
+ throw new Error("메모이제이션 실패");
+ }
+
+ // 메모이제이션 된 함수를 반환한다.
+ return memoizedValueRef.current;
}
diff --git a/packages/lib/src/hooks/useRef.ts b/packages/lib/src/hooks/useRef.ts
index 285d4ae7..e8db0786 100644
--- a/packages/lib/src/hooks/useRef.ts
+++ b/packages/lib/src/hooks/useRef.ts
@@ -1,4 +1,6 @@
+import { useState } from "react";
+
export function useRef(initialValue: T): { current: T } {
- // useState를 이용해서 만들어보세요.
- return { current: initialValue };
+ const [ref] = useState(() => ({ current: initialValue }));
+ return ref;
}
diff --git a/packages/lib/src/hooks/useRouter.ts b/packages/lib/src/hooks/useRouter.ts
index a18ae01f..4a40cb5d 100644
--- a/packages/lib/src/hooks/useRouter.ts
+++ b/packages/lib/src/hooks/useRouter.ts
@@ -6,7 +6,6 @@ import { useShallowSelector } from "./useShallowSelector";
const defaultSelector = (state: T) => state as unknown as S;
export const useRouter = , S>(router: T, selector = defaultSelector) => {
- // useSyncExternalStore를 사용하여 router의 상태를 구독하고 가져오는 훅을 구현합니다.
const shallowSelector = useShallowSelector(selector);
- return shallowSelector(router);
+ return useSyncExternalStore(router.subscribe, () => shallowSelector(router));
};
diff --git a/packages/lib/src/hooks/useShallowSelector.ts b/packages/lib/src/hooks/useShallowSelector.ts
index b75ed791..e7463eb2 100644
--- a/packages/lib/src/hooks/useShallowSelector.ts
+++ b/packages/lib/src/hooks/useShallowSelector.ts
@@ -4,6 +4,19 @@ import { shallowEquals } from "../equals";
type Selector = (state: T) => S;
export const useShallowSelector = (selector: Selector) => {
- // 이전 상태를 저장하고, shallowEquals를 사용하여 상태가 변경되었는지 확인하는 훅을 구현합니다.
- return (state: T): S => selector(state);
+ const prevStateRef = useRef(null);
+
+ return (state: T): S => {
+ if (!prevStateRef.current) {
+ prevStateRef.current = selector(state);
+ return prevStateRef.current;
+ }
+
+ if (shallowEquals(prevStateRef.current, selector(state))) {
+ return prevStateRef.current;
+ }
+
+ prevStateRef.current = selector(state);
+ return prevStateRef.current;
+ };
};
diff --git a/packages/lib/src/hooks/useShallowState.ts b/packages/lib/src/hooks/useShallowState.ts
index 7b589748..42e186d1 100644
--- a/packages/lib/src/hooks/useShallowState.ts
+++ b/packages/lib/src/hooks/useShallowState.ts
@@ -1,7 +1,14 @@
import { useState } from "react";
import { shallowEquals } from "../equals";
+import { useCallback } from "./useCallback.ts";
-export const useShallowState = (initialValue: Parameters>[0]) => {
- // useState를 사용하여 상태를 관리하고, shallowEquals를 사용하여 상태 변경을 감지하는 훅을 구현합니다.
- return useState(initialValue);
+export const useShallowState = (initialValue: T | (() => T)) => {
+ const [state, setState] = useState(initialValue);
+ const setValueShallow = useCallback((newValue: T) => {
+ setState((prevValue) => {
+ return shallowEquals(prevValue, newValue) ? prevValue : newValue;
+ });
+ }, []);
+
+ return [state, setValueShallow] as const;
};
diff --git a/packages/lib/src/hooks/useStorage.ts b/packages/lib/src/hooks/useStorage.ts
index fdc97a6f..f620638c 100644
--- a/packages/lib/src/hooks/useStorage.ts
+++ b/packages/lib/src/hooks/useStorage.ts
@@ -4,6 +4,5 @@ import type { createStorage } from "../createStorage";
type Storage = ReturnType>;
export const useStorage = (storage: Storage) => {
- // useSyncExternalStore를 사용해서 storage의 상태를 구독하고 가져오는 훅을 구현해보세요.
- return storage.get();
+ return useSyncExternalStore(storage.subscribe, storage.get);
};
diff --git a/packages/lib/src/hooks/useStore.ts b/packages/lib/src/hooks/useStore.ts
index acf3ad79..56fa8800 100644
--- a/packages/lib/src/hooks/useStore.ts
+++ b/packages/lib/src/hooks/useStore.ts
@@ -7,7 +7,6 @@ type Store = ReturnType>;
const defaultSelector = (state: T) => state as unknown as S;
export const useStore = (store: Store, selector: (state: T) => S = defaultSelector) => {
- // useSyncExternalStore와 useShallowSelector를 사용해서 store의 상태를 구독하고 가져오는 훅을 구현해보세요.
const shallowSelector = useShallowSelector(selector);
- return shallowSelector(store.getState());
+ return useSyncExternalStore(store.subscribe, () => shallowSelector(store.getState()));
};
diff --git a/packages/lib/src/utils/typeUtil.ts b/packages/lib/src/utils/typeUtil.ts
new file mode 100644
index 00000000..045d97cd
--- /dev/null
+++ b/packages/lib/src/utils/typeUtil.ts
@@ -0,0 +1,16 @@
+export const typeUtils = {
+ isPrimitive(value: unknown) {
+ return (
+ typeof value === "string" ||
+ typeof value === "number" ||
+ typeof value === "boolean" ||
+ typeof value === "bigint" ||
+ typeof value === "symbol" ||
+ value === null ||
+ value === undefined
+ );
+ },
+ isObject(value: unknown): value is Record {
+ return typeof value === "object" && value !== null && !Array.isArray(value);
+ },
+};