Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8팀 김도운] [Chapter 1-3] React, Beyond the Basics #14

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
294 changes: 256 additions & 38 deletions README.md

Large diffs are not rendered by default.

105 changes: 105 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-v8": "^2.1.2",
"@vitest/ui": "^2.1.8",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"husky": "^9.1.7",
"jsdom": "^25.0.1",
Expand Down
40 changes: 40 additions & 0 deletions src/@lib/equalities/deepEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
import { isObject } from "../utils";

export function deepEquals<T>(objA: T, objB: T): boolean {
if (Object.is(objA, objB)) {
return true;
}

if (Array.isArray(objA) && Array.isArray(objB)) {
return _deepEqualArray(objA, objB);
}

if (isObject(objA) && isObject(objB)) {
return _deepEqualObject(
objA as Record<string | number | symbol, unknown>,
objB as Record<string | number | symbol, unknown>,
);
}

return objA === objB;
}

function _deepEqualArray<T>(arrA: Array<T>, arrB: Array<T>): boolean {
if (arrA.length !== arrB.length) return false;
return arrA.every((item, index) => deepEquals(item, arrB[index]));
}

function _deepEqualObject<T extends string | number | symbol>(
objA: Record<T, unknown>,
objB: Record<T, unknown>,
): boolean {
if (objA === objB) return true;

const keysA = Object.keys(objA) as T[];
const keysB = Object.keys(objB) as T[];

if (keysA.length !== keysB.length) {
return false;
}

return keysA.every(
(key) => keysB.includes(key) && deepEquals(objA[key], objB[key]),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 바로 keysB.includes(key)를 사용하는게 keysB 배열을 매 키마다 전체 검색할 것 같아 보이네요 혹시 Set을 사용하는 것이 어떤가요??

Suggested change
(key) => keysB.includes(key) && deepEquals(objA[key], objB[key]),
const setB = new Set(keysB);
return keysA.every(key => setB.has(key) && deepEquals(objA[key], objB[key]));

이렇게 써볼 수 있을 것 같습니다!

);
}
Comment on lines +22 to +43

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크 이렇게 배열 비교와 객체 비교 함수를 따로 분리해서 구현해주신점 너무 좋습니다bb

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 배열일 경우를 따로 분리하신 이유가 있을까요?

45 changes: 44 additions & 1 deletion src/@lib/equalities/shallowEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
import { isObject } from "../utils";

export function shallowEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
if (Object.is(objA, objB)) return true;

if (Array.isArray(objA) && Array.isArray(objB)) {
return _shallowEqualArray(objA, objB);
}

if (isObject(objA) && isObject(objB)) {
return _shallowEqualObject(
objA as Record<string, unknown>,
objB as Record<string, unknown>,
);
}

return false;
}

function _shallowEqualArray<T>(arrA: Array<T>, arrB: Array<T>): boolean {
if (arrA.length !== arrB.length) return false;
return arrA.every((item, index) => item === arrB[index]);
}

function _shallowEqualObject<T extends string | number | symbol>(
objA: Record<T, unknown>,
objB: Record<T, unknown>,
): boolean {
if (objA === objB) return true;

const keysA = Object.keys(objA) as T[];
const keysB = Object.keys(objB) as T[];

if (keysA.length !== keysB.length) {
return false;
}

for (const key of keysA) {
if (
!Object.prototype.hasOwnProperty.call(objB, key) ||
objA[key] !== objB[key]
)
return false;
}
return true;
}
20 changes: 17 additions & 3 deletions src/@lib/hocs/memo.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { shallowEquals } from "../equalities";
import { ComponentType } from "react";
import { ComponentType, createElement, ReactElement } from "react";
import { useRef } from "../hooks";

export function memo<P extends object>(
Component: ComponentType<P>,
_equals = shallowEquals,
) {
return Component;
const MemoizedComponent = (props: P) => {
const prevPropRef = useRef<P | null>(null);
const prevComponentRef = useRef<ReactElement<P> | null>(null);

const shouldUpdate =
prevPropRef.current === null || !_equals(prevPropRef.current, props);

prevPropRef.current = props;
if (shouldUpdate) {
prevComponentRef.current = createElement(Component, props);
}

return prevComponentRef.current;
};
return MemoizedComponent;
}
2 changes: 2 additions & 0 deletions src/@lib/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export * from "./useDeepMemo";
export * from "./useMemo";
export * from "./useCallback";
export * from "./useRef";
export * from "./useContext";
export * from "./useInfiniteScroll";
7 changes: 4 additions & 3 deletions src/@lib/hooks/useCallback.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* 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.ts";

export function useCallback<T extends Function>(
factory: T,
_deps: DependencyList,
) {
// 직접 작성한 useMemo를 통해서 만들어보세요.
return factory as T;
// eslint-disable-next-line react-hooks/exhaustive-deps
return useMemo(() => factory, _deps);
}
40 changes: 40 additions & 0 deletions src/@lib/hooks/useContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createContext, useContext } from "react";
import {
NotificationContextType,
ThemeContextType,
UserContextType,
} from "../types";

export const UserContext = createContext<UserContextType | undefined>(
undefined,
);
export const NotificationContext = createContext<
NotificationContextType | undefined
>(undefined);
export const ThemeContext = createContext<ThemeContextType | undefined>(
undefined,
);

export const useUserContext = () => {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error("useAppContext must be used within an AppProvider");
}
return context;
};

export const useNotificationContext = () => {
const context = useContext(NotificationContext);
if (context === undefined) {
throw new Error("useAppContext must be used within an AppProvider");
}
return context;
};

export const useThemeContext = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useAppContext must be used within an AppProvider");
}
return context;
};
1 change: 0 additions & 1 deletion src/@lib/hooks/useDeepMemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ import { useMemo } from "./useMemo";
import { deepEquals } from "../equalities";

export function useDeepMemo<T>(factory: () => T, deps: DependencyList): T {
// 직접 작성한 useMemo를 참고해서 만들어보세요.
return useMemo(factory, deps, deepEquals);
}
Loading
Loading