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 #9

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
0e90595
feat: prettierrc 파일 추가
Dayglow0926 Dec 29, 2024
e5ff875
feat: shallowEquals 기능 구현
Dayglow0926 Dec 29, 2024
06d60ab
feat: deepEquals 구현
Dayglow0926 Dec 29, 2024
2c4167f
fix: object 비교 수정
Dayglow0926 Dec 29, 2024
b719f07
feat: useRef 기능 추가
Dayglow0926 Dec 29, 2024
0f42d81
feat: useMemo 기능 추가
Dayglow0926 Dec 29, 2024
9bfd803
feat: useCallback 기능 추가
Dayglow0926 Dec 29, 2024
b683d7b
Merge remote-tracking branch 'upstream/main'
Dayglow0926 Dec 30, 2024
4831e14
fix: commit 오류 수정
Dayglow0926 Dec 30, 2024
c3b6c48
feat: hoc memo 기능 추가
Dayglow0926 Dec 30, 2024
4147b9e
feat: type/node 추가
Dayglow0926 Dec 30, 2024
f55908d
feat: App.tsx 리랜더링 방지 코드 추가
Dayglow0926 Dec 30, 2024
a7e01ea
remove: 주석 삭제
Dayglow0926 Jan 1, 2025
98d6739
refactor: ComplexForm 컴포넌트 파일 분리
Dayglow0926 Jan 2, 2025
6e05985
fix: husky pre-commit 수정
Dayglow0926 Jan 2, 2025
73770a6
refactor: Header 컴포넌트 파일 분리
Dayglow0926 Jan 2, 2025
dfbf97b
refactor: ItemList 컴포넌트 파일 분리
Dayglow0926 Jan 2, 2025
4ef15a7
refactor: NotificationSystem 컴포넌트 파일 분리
Dayglow0926 Jan 2, 2025
5505f30
refactor: login context, provider 분리
Dayglow0926 Jan 2, 2025
46864f4
refactor: notification context, provider 분리
Dayglow0926 Jan 2, 2025
65f0398
refactor: theme context, provider 분리
Dayglow0926 Jan 2, 2025
5e0e5b8
refactor: useLogin custom hook 분리
Dayglow0926 Jan 2, 2025
85dfce5
fix: 주석 삭제
Dayglow0926 Jan 2, 2025
ddc5891
refactor: useNotificationContext custom hook 분리
Dayglow0926 Jan 2, 2025
d627fc0
refactor: useThemeContext custom hook 분리
Dayglow0926 Jan 2, 2025
d3ecfc0
refactor: interface 분리 및 폴더 몇 변경
Dayglow0926 Jan 2, 2025
89aa542
refactor: Layout 분리
Dayglow0926 Jan 2, 2025
2d862e0
refactor: App 파일 리팩토링 진행
Dayglow0926 Jan 2, 2025
d2f990a
fix: obj 배열인 경우 길이 체크를 추가함
Dayglow0926 Jan 2, 2025
7d3118d
fix: obj type error 를 해결하기 위해 Record 추가
Dayglow0926 Jan 2, 2025
b23de5d
fix: useRef 초기값 반영방법 변경
Dayglow0926 Jan 2, 2025
b0b1e81
fix: 얕은 비교 기능 수정
Dayglow0926 Jan 2, 2025
0ef3b1d
fix: 깊은 비교 기능 수정
Dayglow0926 Jan 2, 2025
b68e2c3
fix: @types/node 삭제
Dayglow0926 Jan 2, 2025
e0e1a64
Merge remote-tracking branch 'upstream/main'
Dayglow0926 Jan 2, 2025
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
Empty file modified .husky/pre-commit
100644 → 100755
Empty file.
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": false
}
402 changes: 181 additions & 221 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@
"test:ui": "vitest --ui",
"prepare": "husky"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"tsc --noEmit",
"prettier --write",
"eslint --fix"
]
},
"dependencies": {
"@types/node": "^22.10.2",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@playwright/test": "^1.49.1",
Expand All @@ -39,10 +40,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 Down
21 changes: 20 additions & 1 deletion src/@lib/equalities/deepEquals.ts

Choose a reason for hiding this comment

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

오.. 재귀 함수를 잘 이용하셔서 깔끔하게 작성하셨네요! 코드 잘보고 갑니다~

Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
// - 재귀적으로 각 속성에 대해 deepEquals 호출
export function deepEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
if (objA === objB) {
return true;
}
// 2. 둘 다 객체인 경우:
// - 배열인지 확인
if (Array.isArray(objA) && Array.isArray(objB)) {
return objA.every((v, i) => deepEquals(v, objB[i]));
Copy link

Choose a reason for hiding this comment

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

이부분에서도 길이가 같은지 체크하시면 더 안정적인 코드가 될 것 같아요!

}

// - 객체의 키 개수가 다른 경우 처리
if (
typeof objA === "object" &&
typeof objB === "object" &&
Object.keys(objA).length === Object.keys(objB).length
) {
return Object.keys(objA).every((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.

저는 객체 타입이랑 객체 길이 분기 처리를 따로 해놨는데 같이 처리하니 깔끔하네요! 저도 나중에 같이 처리 하도록 수정해야겠습니다ㅎㅎ


return false;
}
31 changes: 30 additions & 1 deletion src/@lib/equalities/shallowEquals.ts
Copy link

Choose a reason for hiding this comment

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

deepEquals와 코드의 구성이 조금 다른 걸로 보이는데 이렇게 구성하신 이유가 있을까요!
틀렸다라는 것보단 궁금해서 여쭤봅니다!

Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
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 keys1 = Object.keys(objA);
const keys2 = Object.keys(objB);
if (keys1.length !== keys2.length) {
return false;
}

// 4. 모든 키에 대해 얕은 비교
for (const key of keys1) {
if (objA[key] !== objB[key]) {
return false;
}
}

return true;
}
30 changes: 26 additions & 4 deletions src/@lib/hocs/memo.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { shallowEquals } from "../equalities";
import { ComponentType } from "react";
import { ComponentType, createElement } from "react";
import { useRef } from "../hooks";

/**
* memo HOC는 컴포넌트의 props를 얕은 비교하여 불필요한 리렌더링을 방지합니다.
* HOC 란?
* Higher-Order Component
* 기본 컴포넌트에 기능 추가하여 업그레이드된 컴포넌트를 만들수 있다.
*/
export function memo<P extends object>(
Component: ComponentType<P>,
_equals = shallowEquals,
equals = shallowEquals,
) {
return Component;
// 2. 메모이제이션된 컴포넌트 생성
const MemoizedComponent = (props: P) => {
// 1. 이전 props를 저장할 ref 생성
const propsRef = useRef<P | null>(null);
const componentRef = useRef<React.ReactElement | null>(null);

// 3. equals 함수를 사용하여 props 비교
if (propsRef.current === null || !equals(props, propsRef.current)) {
propsRef.current = props;
// 4. props가 변경된 경우에만 새로운 렌더링 수행
componentRef.current = createElement(Component, props);
}
Copy link

Choose a reason for hiding this comment

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

저는 아예 컴포넌트를 반환하도록 <Component {...props} />로 했는데 저도 이 방법을 쓸 걸 그랬나봐요!
그러면 안전하게 ts로 반환이 되네요


return componentRef.current;
};

return MemoizedComponent;
}
4 changes: 3 additions & 1 deletion 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 */
import { DependencyList } from "react";
import { useMemo } from "./useMemo";

export function useCallback<T extends Function>(
factory: T,
_deps: DependencyList,
) {
// 직접 작성한 useMemo를 통해서 만들어보세요.
Copy link

Choose a reason for hiding this comment

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

구현 잘 해주셔서 주석 지워주셔도 될 것 같아요!

return factory as T;

return useMemo(() => factory, _deps);
}
16 changes: 15 additions & 1 deletion src/@lib/hooks/useMemo.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { DependencyList } from "react";
import { shallowEquals } from "../equalities";
import { useRef } from "./useRef";

export function useMemo<T>(
factory: () => T,
_deps: DependencyList,
_equals = shallowEquals,
): T {
// 직접 작성한 useRef를 통해서 만들어보세요.
return factory();
// memo 초기값 설정
const ref = useRef<{
deps: DependencyList;
value: T;
initialized: boolean;
}>({ deps: [], value: undefined as T, initialized: false });

Comment on lines +11 to +16
Copy link

Choose a reason for hiding this comment

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

ref를 하나로 구현하셨군요! 좋은 인사이트를 주셔서 감사합니다!

//memo 의존성 검사
if (!ref.current.initialized || !_equals(_deps, ref.current.deps)) {
ref.current.deps = _deps;
ref.current.value = factory();
ref.current.initialized = true;
}
return ref.current.value;
}
9 changes: 8 additions & 1 deletion src/@lib/hooks/useRef.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { useState } from "react";

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

Choose a reason for hiding this comment

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

Suggested change
const [ref] = useState<{ current: T }>({ current: initialValue });
const [ref] = useState<{ current: T }>(() => ({ current: initialValue }));

찾아보니 이렇게 선언하는걸 추천하더라구요!
이유는 렌더링이 비싼 값이 선언되면 매번 리렌더가 발생하기 때문에 제안드린 것과 같이 함수로 구현하는 것을 추천드려요!

/**
* ref.current를 통해 값이 변경이 가능한 이유
* ref(참조값)은 그대로 두고 .current (속성)을 변경하는 것이라 가능
*/
return ref;
}
Loading