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

[6팀 김혜연] [Chapter 1-3] React, Beyond the Basics #52

Open
wants to merge 18 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
995 changes: 664 additions & 331 deletions package-lock.json

Large diffs are not rendered by default.

45 changes: 44 additions & 1 deletion src/@lib/equalities/deepEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
export function deepEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
// 1. 참조가 같은 경우
if (objA === objB) {
return true;
}

// 2. null 또는 undefined 처리
if (objA == null || objB == null) {
return objA === objB;
}

// 3. 타입이 다른 경우
if (typeof objA !== typeof objB) {
return false;
}

// 4. 배열 처리
if (Array.isArray(objA) && Array.isArray(objB)) {
if (objA.length !== objB.length) {
return false;
}

return objA.every((value, index) => deepEquals(value, objB[index]));
}

// 5. 일반 객체 처리
if (typeof objA === "object" && typeof objB === "object") {
const keysA = Object.keys(objA) as (keyof T)[];
const keysB = Object.keys(objB) as (keyof T)[];

// 키 개수 비교
if (keysA.length !== keysB.length) {
return false;
}

// 모든 키와 값을 재귀적으로 비교
return keysA.every(
(key) =>
Object.prototype.hasOwnProperty.call(objB, key) &&
deepEquals(objA[key], objB[key]),
);
}

// 6. 나머지 경우
return false;
}
50 changes: 48 additions & 2 deletions src/@lib/equalities/shallowEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
export function shallowEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
import { DependencyList } from "react";

export function shallowEquals<T>(
objA: T | DependencyList,
objB: T | DependencyList,
): boolean {
// 1. 두 값이 정확히 같은지 확인 (참조가 같은 경우)
if (objA === objB) {
return true;
}

// 2. 둘 중 하나라도 null이거나 객체/배열이 아닌 경우 처리
if (
typeof objA !== "object" ||
typeof objB !== "object" ||
objA === null ||
objB === null
) {
return false;
}

// 3. 배열인 경우 처리
if (Array.isArray(objA) && Array.isArray(objB)) {
if (objA.length !== objB.length) {
return false;
}

return !objA.some((item, index) => item !== objB[index]);
}

// 4. 배열이 아닌 경우 객체로 간주하고 처리
if (!Array.isArray(objA) && !Array.isArray(objB)) {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

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

return keysA.every(
(key) =>
(objA as Record<string, unknown>)[key] ===
(objB as Record<string, unknown>)[key],
);
}

// 5. 한쪽이 배열이고 한쪽이 객체인 경우
return false;
}
3 changes: 1 addition & 2 deletions src/@lib/hocs/deepMemo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { deepEquals } from "../equalities";
import { ComponentType } from "react";
import { memo } from "./memo.ts";

export function deepMemo<P extends object>(Component: ComponentType<P>) {
export function deepMemo(Component: React.FC) {
return memo(Component, deepEquals);
}
28 changes: 22 additions & 6 deletions src/@lib/hocs/memo.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { createElement, ReactNode } from "react";
import { shallowEquals } from "../equalities";
import { ComponentType } from "react";
import { useRef } from "../hooks";

export function memo<P extends object>(
Component: ComponentType<P>,
_equals = shallowEquals,
) {
return Component;
Component: React.FC<P>,
equals = shallowEquals,
): React.FC<P> {
const MemoizedComponent: React.FC<P> = (props) => {
const prevPropsRef = useRef<P | null>(null);
const renderedRef = useRef<ReactNode>(null);

if (
prevPropsRef.current === null ||
!equals(prevPropsRef.current, props) ||
renderedRef.current === null
) {
prevPropsRef.current = props;
renderedRef.current = createElement(Component, props);
}

return renderedRef.current as ReactNode;
};

return MemoizedComponent;
}
12 changes: 5 additions & 7 deletions src/@lib/hooks/useCallback.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
/* 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;
export function useCallback<
T extends (...args: Parameters<T>) => ReturnType<T>,
>(callback: T, deps: DependencyList): T {
return useMemo(() => callback, deps);
}
18 changes: 13 additions & 5 deletions src/@lib/hooks/useDeepMemo.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { DependencyList } from "react";
import { useMemo } from "./useMemo";
import { DependencyList, useRef } from "react";
import { deepEquals } from "../equalities";

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

const dependenciesChanged =
!lastDepsRef.current || !deepEquals(lastDepsRef.current, deps);

if (dependenciesChanged) {
lastValueRef.current = factory();
lastDepsRef.current = deps;
}

return lastValueRef.current as T;
}
27 changes: 20 additions & 7 deletions src/@lib/hooks/useMemo.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { DependencyList } from "react";
import { shallowEquals } from "../equalities";
import { DependencyList, useRef } from "react";

export function useMemo<T>(
factory: () => T,
_deps: DependencyList,
_equals = shallowEquals,
deps: DependencyList,
equals: (a: DependencyList, b: DependencyList) => boolean = (a, b) =>
a.length === b.length && a.every((v, i) => v === b[i]),
): T {
// 직접 작성한 useRef를 통해서 만들어보세요.
return factory();
// 이전 의존성 저장
const previousDepsRef = useRef<DependencyList | null>(null);
// 메모이제이션된 값 저장
const memoizedValueRef = useRef<T | null>(null);

// 의존성 변경 감지
const dependenciesChanged =
!previousDepsRef.current || !equals(previousDepsRef.current, deps);

if (dependenciesChanged) {
// 의존성이 변경되었을 경우 재계산
memoizedValueRef.current = factory();
previousDepsRef.current = deps;
}

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

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

return ref;
}
Loading
Loading