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

[7팀 김원표] [Chapter 1-3] React, Beyond the Basics #38

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
npx lint-staged
npm run tsc && npm run prettier:write && npm run lint:fix
2,846 changes: 2,632 additions & 214 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 8 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,26 @@
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"tsc --noEmit",
"prettier --write",
"eslint --fix"
]
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@playwright/test": "^1.49.1",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.10.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"@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",
"@vitest/ui": "^2.1.8",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.3",
"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
34 changes: 33 additions & 1 deletion src/@lib/equalities/deepEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
// deepEquals 함수는 두 값의 깊은 비교를 수행합니다.
export function deepEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
// 1. 기본 타입이거나 null인 경우 처리
if (objA === objB) return true;

if (
typeof objA !== "object" ||
objA === null ||
typeof objB !== "object" ||
objB === null
) {
return false;
}
Comment on lines +6 to +13
Copy link

Choose a reason for hiding this comment

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

위에서 primitive 타입을 걸러내고 여기서 따로 null과 undefined를 찾는 방법이네요!
저와는 다른 방식으로 접근하셔서 좋은 인사이트를 얻었습니다!


// 2. 둘 다 객체인 경우:
// 배열인지 확인
if (Array.isArray(objA) !== Array.isArray(objB)) {
return false;
}

// 객체의 키 개수가 다른 경우 처리
const keysA = Object.keys(objA) as (keyof T)[];
const keysB = Object.keys(objB) as (keyof T)[];

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

// 재귀적으로 각 속성에 대해 deepEquals 호출
for (const key of keysA) {
if (!deepEquals(objA[key], objB[key])) {
return false;
}
}

return true;
}
31 changes: 30 additions & 1 deletion src/@lib/equalities/shallowEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
//shallowEquals 함수는 두 값의 얕은 비교를 수행합니다.
export function shallowEquals<T>(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]
Comment on lines +24 to +25
Copy link

Choose a reason for hiding this comment

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

hasOwnProperty 를 사용해서 명시적이고 안전하게 키 검증이 되어서 좋아보이네요!

) {
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 React, { ComponentType } from "react";
import { useRef } from "../hooks";

// memo HOC는 컴포넌트의 props를 얕은 비교하여 불필요한 리렌더링을 방지합니다.
export function memo<P extends object>(
Component: ComponentType<P>,
_equals = shallowEquals,
) {
return Component;
const MemoizedComponent = (props: P) => {
// 1. 이전 props를 저장할 ref 생성
const prevProps = useRef<P | null>(null);

// 2. 메모이제이션된 컴포넌트 생성
// 3. equals 함수를 사용하여 props 비교
if (prevProps.current === null || !_equals(prevProps.current, props)) {
prevProps.current = props;
return React.createElement(Component, prevProps.current);
}
Copy link

Choose a reason for hiding this comment

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

저는 .tsx로 변경하고 <Component {...props} /> 로 적용했거든요! 다른 분들도 이렇게 적용하시더라구요! 저도 이 방식으로 적용해볼 걸 그랬나봐요!

};
// 4. props가 변경된 경우에만 새로운 렌더링 수행

return MemoizedComponent;
}
6 changes: 4 additions & 2 deletions src/@lib/hooks/useCallback.ts
Original file line number Diff line number Diff line change
@@ -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<T extends Function>(
factory: T,
_deps: DependencyList,
) {
// 직접 작성한 useMemo를 통해서 만들어보세요.
return factory as T;
// 함수와 의존성 배열을 인자로 받고, 반환 값을 메모이제이션된 함수
return useMemo(() => factory, _deps);
}
19 changes: 17 additions & 2 deletions src/@lib/hooks/useMemo.ts
Original file line number Diff line number Diff line change
@@ -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<T>(
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,
});

Comment on lines +13 to +16
Copy link

Choose a reason for hiding this comment

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

저는 이부분에 2개의 useRef를 선언했거든요!
depsRefref를 각각 선언하고 각각 값을 할당해주는 방식으로 진행했어요! 이 부분도 오히려 이렇게 하나로 하는게 더 좋아보이네요!

// 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;
}
9 changes: 7 additions & 2 deletions src/@lib/hooks/useRef.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export function useRef<T>(initialValue: T): { current: T } {
// useRef 훅은 렌더링 사이에 값을 유지하는 가변 ref 객체를 생성합니다.
import { useState } from "react";

export function useRef<T>(initialValue: T | null): { current: T | null } {
// React의 useState를 이용해서 만들어보세요.
return { current: initialValue };
const [ref] = useState(() => ({ current: initialValue }));

return ref;
}
Loading
Loading