(objA: T, objB: T): boolean {
- return objA === objB;
+ // 1. 두 값이 정확히 같은지 확인 (참조가 같은 경우)
+ if (objA === objB) return true;
+
+ // 2. 둘 중 하나라도 객체가 아닌 경우 처리
+ if (
+ typeof objA !== "object" ||
+ objA === null ||
+ typeof objB !== "object" ||
+ objB === null
+ ) {
+ return false;
+ }
+
+ // 3. 객체의 키 개수가 다른 경우 처리
+ const keysA = Object.keys(objA) as (keyof T)[];
+ const keysB = Object.keys(objB) as (keyof T)[];
+ if (keysA.length !== keysB.length) return false;
+
+ // 4. 모든 키에 대해 얕은 비교 수행
+ for (const key of keysA) {
+ if (
+ !Object.prototype.hasOwnProperty.call(objB, key) ||
+ objA[key] !== objB[key]
+ ) {
+ return false;
+ }
+ }
+
+ return true;
}
diff --git a/src/@lib/hocs/memo.ts b/src/@lib/hocs/memo.ts
index d43559d..e573637 100644
--- a/src/@lib/hocs/memo.ts
+++ b/src/@lib/hocs/memo.ts
@@ -1,10 +1,24 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
import { shallowEquals } from "../equalities";
-import { ComponentType } from "react";
+import React, { ComponentType } from "react";
+import { useRef } from "../hooks";
+// memo HOC는 컴포넌트의 props를 얕은 비교하여 불필요한 리렌더링을 방지합니다.
export function memo(
Component: ComponentType
,
_equals = shallowEquals,
) {
- return Component;
+ const MemoizedComponent = (props: P) => {
+ // 1. 이전 props를 저장할 ref 생성
+ const prevProps = useRef
(null);
+
+ // 2. 메모이제이션된 컴포넌트 생성
+ // 3. equals 함수를 사용하여 props 비교
+ if (prevProps.current === null || !_equals(prevProps.current, props)) {
+ prevProps.current = props;
+ return React.createElement(Component, prevProps.current);
+ }
+ };
+ // 4. props가 변경된 경우에만 새로운 렌더링 수행
+
+ return MemoizedComponent;
}
diff --git a/src/@lib/hooks/useCallback.ts b/src/@lib/hooks/useCallback.ts
index e71e647..8050d31 100644
--- a/src/@lib/hooks/useCallback.ts
+++ b/src/@lib/hooks/useCallback.ts
@@ -1,10 +1,12 @@
-/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-unsafe-function-type */
+/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import { DependencyList } from "react";
+import { useMemo } from "./useMemo";
export function useCallback(
factory: T,
_deps: DependencyList,
) {
// 직접 작성한 useMemo를 통해서 만들어보세요.
- return factory as T;
+ // 함수와 의존성 배열을 인자로 받고, 반환 값을 메모이제이션된 함수
+ return useMemo(() => factory, _deps);
}
diff --git a/src/@lib/hooks/useMemo.ts b/src/@lib/hooks/useMemo.ts
index 95930d6..e6b11e9 100644
--- a/src/@lib/hooks/useMemo.ts
+++ b/src/@lib/hooks/useMemo.ts
@@ -1,12 +1,27 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
import { DependencyList } from "react";
import { shallowEquals } from "../equalities";
+import { useRef } from "./useRef";
+// useMemo 훅은 계산 비용이 높은 값을 메모이제이션합니다.
export function useMemo(
factory: () => T,
_deps: DependencyList,
_equals = shallowEquals,
): T {
// 직접 작성한 useRef를 통해서 만들어보세요.
- return factory();
+ // 1. 이전 의존성과 결과를 저장할 ref 생성
+ const ref = useRef<{ deps: DependencyList | null; value: T | null }>({
+ deps: null,
+ value: null,
+ });
+
+ // 2. 현재 의존성과 이전 의존성 비교
+ if (ref.current?.deps === null || !_equals(ref.current?.deps, _deps)) {
+ // 3. 의존성이 변경된 경우 factory 함수 실행 및 결과 저장
+ ref.current.value = factory();
+ ref.current.deps = _deps;
+ }
+
+ // 4. 메모이제이션된 값 반환
+ return ref.current?.value as T;
}
diff --git a/src/@lib/hooks/useRef.ts b/src/@lib/hooks/useRef.ts
index 2dc9e83..8654d45 100644
--- a/src/@lib/hooks/useRef.ts
+++ b/src/@lib/hooks/useRef.ts
@@ -1,4 +1,9 @@
-export function useRef(initialValue: T): { current: T } {
+// useRef 훅은 렌더링 사이에 값을 유지하는 가변 ref 객체를 생성합니다.
+import { useState } from "react";
+
+export function useRef(initialValue: T | null): { current: T | null } {
// React의 useState를 이용해서 만들어보세요.
- return { current: initialValue };
+ const [ref] = useState(() => ({ current: initialValue }));
+
+ return ref;
}
diff --git a/src/App.tsx b/src/App.tsx
index debd645..7957145 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,11 @@
-import React, { useState, createContext, useContext } from "react";
+import React, {
+ useState,
+ createContext,
+ useContext,
+ useCallback,
+ useMemo,
+ PropsWithChildren,
+} from "react";
import { generateItems, renderLog } from "./utils";
// 타입 정의
@@ -21,33 +28,117 @@ interface Notification {
type: "info" | "success" | "warning" | "error";
}
-// AppContext 타입 정의
-interface AppContextType {
+// Context 정의
+interface ThemeContextType {
theme: string;
toggleTheme: () => void;
+}
+
+interface UserConTextType {
user: User | null;
login: (email: string, password: string) => void;
logout: () => void;
+}
+
+interface NotificationContextType {
notifications: Notification[];
addNotification: (message: string, type: Notification["type"]) => void;
removeNotification: (id: number) => void;
}
-const AppContext = createContext(undefined);
+const ThemeContext = createContext(null);
+const UserContext = createContext(null);
+const NotificationContext = createContext(null);
+
+const ThemeProvider: React.FC = ({ children }) => {
+ const [theme, setTheme] = useState("light");
+ const toggleTheme = useCallback(() => {
+ setTheme((prev) => (prev === "light" ? "dark" : "lht"));
+ }, []);
+ const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
+ return (
+ {children}
+ );
+};
+
+const UserProvider: React.FC = ({ children }) => {
+ const [user, setUser] = useState(null);
+ const { addNotification } = useNotification();
+
+ const login = useCallback(
+ (name: string, email: string) => {
+ setUser({ id: 1, name, email });
+ addNotification("Login successful", "success");
+ },
+ [addNotification],
+ );
+ const logout = useCallback(() => {
+ setUser(null);
+ addNotification("Logout successful", "info");
+ }, [addNotification]);
+
+ const value = useMemo(() => ({ user, login, logout }), [user, login, logout]);
+
+ return {children};
+};
+
+const NotificationProvider: React.FC = ({ children }) => {
+ const [notifications, setNotifications] = useState([]);
+
+ const addNotification = useCallback(
+ (message: string, type: Notification["type"]) => {
+ setNotifications((prev) => [...prev, { id: Date.now(), message, type }]);
+ },
+ [],
+ );
+
+ const removeNotification = useCallback((id: number) => {
+ setNotifications((prev) => prev.filter((notif) => notif.id !== id));
+ }, []);
+
+ const value = useMemo(
+ () => ({
+ notifications,
+ addNotification,
+ removeNotification,
+ }),
+ [notifications, addNotification, removeNotification],
+ );
+
+ return (
+
+ {children}
+
+ );
+};
-// 커스텀 훅: useAppContext
-const useAppContext = () => {
- const context = useContext(AppContext);
- if (context === undefined) {
- throw new Error("useAppContext must be used within an AppProvider");
- }
+// 커스텀 훅
+const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) throw new Error("useTheme must be used within a ThemeProvider");
+ return context;
+};
+
+const useUser = () => {
+ const context = useContext(UserContext);
+ if (!context) throw new Error("useUser must be used within a UserProvider");
+ return context;
+};
+
+const useNotification = () => {
+ const context = useContext(NotificationContext);
+ if (!context)
+ throw new Error(
+ "useNotification must be used within a NotificationProvider",
+ );
return context;
};
// Header 컴포넌트
export const Header: React.FC = () => {
renderLog("Header rendered");
- const { theme, toggleTheme, user, login, logout } = useAppContext();
+ const { theme, toggleTheme } = useTheme();
+ const { user, login, logout } = useUser();
const handleLogin = () => {
// 실제 애플리케이션에서는 사용자 입력을 받아야 합니다.
@@ -96,7 +187,7 @@ export const ItemList: React.FC<{
}> = ({ items, onAddItemsClick }) => {
renderLog("ItemList rendered");
const [filter, setFilter] = useState("");
- const { theme } = useAppContext();
+ const { theme } = useTheme();
const filteredItems = items.filter(
(item) =>
@@ -151,7 +242,7 @@ export const ItemList: React.FC<{
// ComplexForm 컴포넌트
export const ComplexForm: React.FC = () => {
renderLog("ComplexForm rendered");
- const { addNotification } = useAppContext();
+ const { addNotification } = useNotification();
const [formData, setFormData] = useState({
name: "",
email: "",
@@ -236,7 +327,7 @@ export const ComplexForm: React.FC = () => {
// NotificationSystem 컴포넌트
export const NotificationSystem: React.FC = () => {
renderLog("NotificationSystem rendered");
- const { notifications, removeNotification } = useAppContext();
+ const { notifications, removeNotification } = useNotification();
return (
@@ -268,14 +359,7 @@ export const NotificationSystem: React.FC = () => {
// 메인 App 컴포넌트
const App: React.FC = () => {
- const [theme, setTheme] = useState("light");
const [items, setItems] = useState(generateItems(1000));
- const [user, setUser] = useState
(null);
- const [notifications, setNotifications] = useState([]);
-
- const toggleTheme = () => {
- setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
- };
const addItems = () => {
setItems((prevItems) => [
@@ -284,61 +368,25 @@ const App: React.FC = () => {
]);
};
- const login = (email: string) => {
- setUser({ id: 1, name: "홍길동", email });
- addNotification("성공적으로 로그인되었습니다", "success");
- };
-
- const logout = () => {
- setUser(null);
- addNotification("로그아웃되었습니다", "info");
- };
-
- const addNotification = (message: string, type: Notification["type"]) => {
- const newNotification: Notification = {
- id: Date.now(),
- message,
- type,
- };
- setNotifications((prev) => [...prev, newNotification]);
- };
-
- const removeNotification = (id: number) => {
- setNotifications((prev) =>
- prev.filter((notification) => notification.id !== id),
- );
- };
-
- const contextValue: AppContextType = {
- theme,
- toggleTheme,
- user,
- login,
- logout,
- notifications,
- addNotification,
- removeNotification,
- };
-
return (
-
-
-
-
-
-
+
+
+
+
);
};
diff --git a/tsconfig.json b/tsconfig.json
index 9818dbe..1ffef60 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,7 @@
{
"files": [],
"references": [
- { "path": "./tsconfig.app.json"},
- { "path": "./tsconfig.node.json"}
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
]
}