-
Notifications
You must be signed in to change notification settings - Fork 56
[8팀 현지수] Chapter 1-3. React, Beyond the Basics #42
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
base: main
Are you sure you want to change the base?
Changes from all commits
773bec1
c706141
8d9b1ce
d2385f5
86bab8f
b0e9c04
1c31c75
e0728dc
24f9de2
b7fb439
4ed7d45
cdb9336
beea35d
e18ebf8
19cbe5d
ce3948d
c5ca529
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <!doctype html> | ||
| <html lang="ko"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>상품 쇼핑몰</title> | ||
| <script src="https://cdn.tailwindcss.com"></script> | ||
| <link rel="stylesheet" href="/src/styles.css" /> | ||
| <script> | ||
| tailwind.config = { | ||
| theme: { | ||
| extend: { | ||
| colors: { | ||
| primary: "#3b82f6", | ||
| secondary: "#6b7280", | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| </head> | ||
| <body class="bg-gray-50"> | ||
| <div id="root"></div> | ||
| <script type="module" src="/src/main.tsx"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,34 @@ | ||
| export const deepEquals = (a: unknown, b: unknown) => { | ||
| return a === b; | ||
| }; | ||
| /** | ||
| * 두 값의 깊은 비교를 수행합니다. (모든 중첩 레벨에서 재귀적으로 비교) | ||
| * | ||
| * @param objA - 비교할 첫 번째 값 (모든 타입 허용) | ||
| * @param objB - 비교할 두 번째 값 (모든 타입 허용) | ||
| * @returns 두 값이 깊은 수준에서 동일하면 true, 다르면 false | ||
| */ | ||
| export function deepEquals(objA: unknown, objB: unknown): boolean { | ||
| // 1. 참조 동일성 체크 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이부분은 제생각보다 훨신 더 잘 구성해주셨네요, AI를 돌려도 detail이 다르긴하네요... typeof objA !== typeof objBai 돌려도 굳이 이 코드 없어도 된다 이정도만 지적해주는거보면 지수님이 코드를 잘 구성하시는 편이시네요 |
||
| if (objA === objB) return true; | ||
|
|
||
| // 2. 타입 및 null 검증 | ||
| if (typeof objA !== typeof objB) return false; | ||
| if (objA === null || objB === null) return false; | ||
| if (typeof objA !== "object") return false; | ||
|
|
||
| // 3. 배열 타입 일치성 확인 | ||
| if (Array.isArray(objA) !== Array.isArray(objB)) return false; | ||
|
|
||
| const recordA = objA as Record<string, unknown>; | ||
| const recordB = objB as Record<string, unknown>; | ||
|
|
||
| // 4. 키 개수 비교 | ||
| const keysA = Object.keys(recordA); | ||
| if (keysA.length !== Object.keys(recordB).length) return false; | ||
|
|
||
| // 5. 키별 재귀적 깊은 비교 | ||
| for (const key of keysA) { | ||
| if (!(key in recordB)) return false; | ||
| if (!deepEquals(recordA[key], recordB[key])) return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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,31 @@ | ||
| export const shallowEquals = (a: unknown, b: unknown) => { | ||
| return a === b; | ||
| }; | ||
| /** | ||
| * 두 값의 얕은 비교를 수행합니다. (1단계 깊이만 비교) | ||
| * | ||
| * @param objA - 비교할 첫 번째 값 (모든 타입 허용) | ||
| * @param objB - 비교할 두 번째 값 (모든 타입 허용) | ||
| * @returns 두 값이 얕은 수준에서 동일하면 true, 다르면 false | ||
| */ | ||
| export function shallowEquals(objA: unknown, objB: unknown): boolean { | ||
| // 1. 참조 동일성 체크 | ||
| if (objA === objB) return true; | ||
|
|
||
| // 2. 타입 및 null 검증 | ||
| if (typeof objA !== typeof objB) return false; | ||
| if (objA === null || objB === null) return false; | ||
| if (typeof objA !== "object") return false; | ||
|
|
||
| const recordA = objA as Record<string, unknown>; | ||
| const recordB = objB as Record<string, unknown>; | ||
|
|
||
| // 3. 키 개수 비교 | ||
| const keysA = Object.keys(recordA); | ||
| if (keysA.length !== Object.keys(recordB).length) return false; | ||
|
|
||
| // 4. 키별 얕은 비교 | ||
| for (const key of keysA) { | ||
| if (!(key in recordB)) return false; | ||
| if (recordA[key] !== recordB[key]) return false; | ||
| } | ||
|
|
||
| return true; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,12 @@ | ||
| import type { FunctionComponent } from "react"; | ||
| import { memo, type FunctionComponent } from "react"; | ||
| import { deepEquals } from "../equals"; | ||
|
|
||
| /** | ||
| * 컴포넌트의 props를 깊은 비교하여 불필요한 리렌더링을 방지하는 HOC입니다. | ||
| * | ||
| * @param Component - 메모이제이션할 함수형 컴포넌트 | ||
| * @returns 메모이제이션된 컴포넌트 | ||
| */ | ||
| export function deepMemo<P extends object>(Component: FunctionComponent<P>) { | ||
| return Component; | ||
| return memo(Component, deepEquals); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,27 @@ | ||
| import { type FunctionComponent } from "react"; | ||
| import { type FunctionComponent, type ReactNode } from "react"; | ||
| import { shallowEquals } from "../equals"; | ||
| import { useRef } from "../hooks/useRef"; | ||
|
|
||
| export function memo<P extends object>(Component: FunctionComponent<P>, equals = shallowEquals) { | ||
| return Component; | ||
| /** | ||
| * 컴포넌트의 props를 얕은 비교하여 불필요한 리렌더링을 방지 하는 HOC입니다. | ||
| * | ||
| * @param Component - 메모이제이션할 함수형 컴포넌트 | ||
| * @param equals - props 비교에 사용할 함수 (기본값: shallowEquals) | ||
| * @returns 메모이제이션된 컴포넌트 | ||
| */ | ||
| export function memo<P extends object>(Component: FunctionComponent<P>, equals = shallowEquals): FunctionComponent<P> { | ||
| return function MemoizedComponent(props: P) { | ||
| // 이전 props와 렌더링 결과를 저장할 ref들 | ||
| const prevPropsRef = useRef<P | null>(null); | ||
| const memoizedResultRef = useRef<ReactNode | Promise<ReactNode> | null>(null); | ||
|
|
||
| // 첫 렌더링이거나 props가 변경된 경우에만 새로 렌더링 | ||
| if (prevPropsRef.current === null || !equals(prevPropsRef.current, props)) { | ||
| memoizedResultRef.current = Component(props); | ||
| prevPropsRef.current = props; | ||
| } | ||
|
|
||
| // 캐시된 결과 반환 | ||
| return memoizedResultRef.current!; | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,15 @@ | ||
| /* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-unsafe-function-type */ | ||
| import type { DependencyList } from "react"; | ||
| import { useMemo } from "./useMemo"; | ||
|
|
||
| export function useCallback<T extends Function>(factory: T, _deps: DependencyList) { | ||
| // 직접 작성한 useMemo를 통해서 만들어보세요. | ||
| return factory as T; | ||
| /** | ||
| * 함수를 메모이제이션합니다. | ||
| * | ||
| * @param factory - 메모이제이션할 함수 | ||
| * @param deps - 의존성 배열 | ||
| * @returns 메모이제이션된 함수 | ||
| */ | ||
| export function useCallback<T extends Function>(factory: T, deps: DependencyList): T { | ||
| // useMemo를 사용해서 함수 자체를 메모이제이션 | ||
| return useMemo(() => factory, deps); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,26 @@ | ||
| /* eslint-disable @typescript-eslint/no-unused-vars */ | ||
| import type { DependencyList } from "react"; | ||
| import { shallowEquals } from "../equals"; | ||
| import { useRef } from "./useRef"; | ||
|
|
||
| export function useMemo<T>(factory: () => T, _deps: DependencyList, _equals = shallowEquals): T { | ||
| // 직접 작성한 useRef를 통해서 만들어보세요. | ||
| return factory(); | ||
| /** | ||
| * 계산 비용이 높은 값을 메모이제이션합니다. | ||
| * | ||
| * @param factory - 메모이제이션할 값을 계산하는 함수 | ||
| * @param deps - 의존성 배열 | ||
| * @param equals - 의존성 비교에 사용할 함수 (기본값: shallowEquals) | ||
| * @returns 메모이제이션된 값 | ||
| */ | ||
| export function useMemo<T>(factory: () => T, deps: DependencyList, equals = shallowEquals): T { | ||
| // 이전 의존성과 계산 결과를 저장할 ref | ||
| const ref = useRef<{ deps: DependencyList; value: T } | null>(null); | ||
|
|
||
| // 첫 호출이거나 의존성이 변경된 경우 재계산 | ||
| if (ref.current === null || !equals(ref.current.deps, deps)) { | ||
| const value = factory(); | ||
| ref.current = { deps, value }; // 새 의존성과 결과 저장 | ||
| } | ||
|
|
||
| // 메모이제이션된 값 반환 | ||
| return ref.current.value; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,12 @@ | ||
| import { useState } from "react"; | ||
|
|
||
| /** | ||
| * 렌더링 사이에 값을 유지하는 가변 ref 객체를 생성합니다. | ||
| * | ||
| * @param initialValue - ref 객체의 초기값 (모든 타입 허용) | ||
| * @returns current 속성을 가진 가변 객체 ({ current: T }) | ||
| */ | ||
| export function useRef<T>(initialValue: T): { current: T } { | ||
| // useState를 이용해서 만들어보세요. | ||
| return { current: initialValue }; | ||
| const [ref] = useState(() => ({ current: initialValue })); | ||
| return ref; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지수님도 유열님이랑 동일하게
useAutoCallback으로 진행해 주셨군요, 혹시useAutoCallback으로 진행한 이유를 알수 있을까요? 지수님 생각이 듣고싶네요그와 별개로 코드는 굉장히 잘 구성해주셧네요
재사용 가능한 유틸로 만들고 싶다고 하면, 지수님이 남겨준
https://github.com/hanghae-plus/front_6th_chapter1-3/pull/45/files#r2230056660
가 있습니다.
지수님 코드 우라까이 한거지만 확인해보면 좋을 것 같아요