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

[13팀 김보영] [Chapter 1-3] React, Beyond the Basics #46

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package-lock.json

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

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-v8": "^2.1.2",
"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 All @@ -44,6 +44,6 @@
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1",
"vitest": "^2.1.2"
"vitest": "^2.1.8"
}
}
21 changes: 20 additions & 1 deletion src/@lib/equalities/deepEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
export function deepEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
if (objA === objB) return true;

if (objA === null || objB === null || typeof objA !== "object" || typeof objB !== "object") return false;

const isArrayA = Array.isArray(objA);
const isArrayB = Array.isArray(objB);
if (isArrayA && isArrayB) {
if (objA.length !== objB.length) return false;
return objA.every((item, index) => deepEquals(item, objB[index]));
}

const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false;

for (const key of keysA)
if (!deepEquals(objA[key as keyof T], objB[key as keyof T]))
return false

return true;
}
17 changes: 15 additions & 2 deletions src/@lib/equalities/shallowEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
export function shallowEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
}
if (objA === objB) return true;

if (objA === null || objB === null ||
typeof objA !== "object" || typeof objB !== "object") return false;

const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false;

for (const key of keysA) {
if (objA[key as keyof T] !== objB[key as keyof T]) return false;
}

return true;
}
1 change: 1 addition & 0 deletions src/@lib/hocs/deepMemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { deepEquals } from "../equalities";
import { ComponentType } from "react";
import { memo } from "./memo.ts";

// deepMemo HOC는 컴포넌트의 props를 깊은 비교하여 불필요한 리렌더링을 방지합니다.
export function deepMemo<P extends object>(Component: ComponentType<P>) {
return memo(Component, deepEquals);
}
24 changes: 22 additions & 2 deletions src/@lib/hocs/memo.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { shallowEquals } from "../equalities";
import { ComponentType } from "react";
import { ComponentType, createElement, useRef } from "react";

// memo HOC는 컴포넌트의 props를 얕은 비교하여 불필요한 리렌더링을 방지합니다.
export function memo<P extends object>(
Component: ComponentType<P>,
_equals = shallowEquals,
) {
return Component;
// 1. 이전 props를 저장할 ref 생성
// 2. 메모이제이션된 컴포넌트 생성
// 3. equals 함수를 사용하여 props 비교
// 4. props가 변경된 경우에만 새로운 렌더링 수행

return function(props: P) {
const prevPropsRef = useRef<P | null>(null);
const memoizedResultRef = useRef<JSX.Element | null>(null);

if (prevPropsRef.current === null || !_equals(prevPropsRef.current, props)) {
console.log("Props changed, re-rendering");
memoizedResultRef.current = createElement(Component, props);
} else {
console.log("Props unchanged, using memoized result");
}

prevPropsRef.current = props;

return memoizedResultRef.current!;
};
}
5 changes: 3 additions & 2 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 */
import { DependencyList } from "react";
import { useMemo } from "./useMemo";

export function useCallback<T extends Function>(
factory: T,
_deps: DependencyList,
) {
// 직접 작성한 useMemo를 통해서 만들어보세요.
return factory as T;
const memoizedCallback = useMemo(() => factory, _deps);
return memoizedCallback;
}
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);
}
29 changes: 25 additions & 4 deletions src/@lib/hooks/useMemo.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { DependencyList } from "react";
import { shallowEquals } from "../equalities";
import { useRef } from "./useRef";

// useMemo 훅은 계산 비용이 높은 값을 메모이제이션합니다.
export function useMemo<T>(
factory: () => T,
_deps: DependencyList,
factory: () => T,
_deps: DependencyList,
_equals = shallowEquals,
): T {
// 직접 작성한 useRef를 통해서 만들어보세요.
return factory();
// 1. 이전 의존성과 결과를 저장할 ref 생성
// 2. 현재 의존성과 이전 의존성 비교
// 3. 의존성이 변경된 경우 factory 함수 실행 및 결과 저장
// 4. 메모이제이션된 값 반환

const memoized = useRef<{
deps: DependencyList | null;
value: T | null;
}>({
deps: null,
value: null,
});

const equals = !memoized.current.deps || !_equals(memoized.current.deps, _deps);

if (equals) {
memoized.current.value = factory();
memoized.current.deps = _deps;
}

return memoized.current.value as T;
}
8 changes: 6 additions & 2 deletions src/@lib/hooks/useRef.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { useState } from "react";

// useRef 훅은 렌더링 사이에 값을 유지하는 가변 ref 객체를 생성합니다.
export function useRef<T>(initialValue: T): { current: T } {
// React의 useState를 이용해서 만들어보세요.
return { current: initialValue };
const [ref] = useState(() => ({current: initialValue}));

return ref;
}
Loading
Loading