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

[11팀 임재도] [Chapter 1-3] React, Beyond the Basics #41

Open
wants to merge 19 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
50 changes: 49 additions & 1 deletion src/@lib/equalities/deepEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
export function deepEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
// 1. 기본 타입이거나 null인 경우 처리
// primitive type 외에는 모두 Object를 상속받음.
// symbole은 deepEquals 시 비교 X -> 필요시 추후 구현
// Q. 그냥 objA === objB로 비교해도 되지 않나?
if (
typeof objA !== "object" ||
objA === null ||
typeof objB !== "object" ||
objB === null
) {
return objA === objB;
}
Comment on lines +6 to +13

Choose a reason for hiding this comment

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

오 기본형 데이터를 한 번에 처리하신 것 같은데 이렇게 합칠 수도 있군요!👍


// 2. 둘 다 객체인 경우:
// - 배열인지 확인
// - 객체의 키 개수가 다른 경우 처리
// - 재귀적으로 각 속성에 대해 deepEquals 호출
if (Array.isArray(objA) && Array.isArray(objB)) {
if (objA.length !== objB.length) {
return false;
}

for (let i = 0; i < objA.length; i++) {
if (!deepEquals(objA[i], objB[i])) {
return false;
}
}
} else {
// 두 객체의 키 개수가 다른 경우
const keysA = Object.keys(objA) as (keyof T)[];
const keysB = Object.keys(objB) as (keyof T)[];

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

for (const key of keysA) {
if (
// general하게 사용되는 deepEquals이므로, Object.prototype.hasOwnProperty를 사용 (call로 매핑해줌으로써, 예외상황을 처리)
!Object.prototype.hasOwnProperty.call(objB, key) ||
!deepEquals(objA[key], objB[key])
) {
return false;
}
}
}
Comment on lines +15 to +47

Choose a reason for hiding this comment

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

shallowEquals랑 다르게 배열을 별도로 처리하신 이유가 궁금합니다!


// 이 부분을 적절히 수정하세요.
return true;
}
36 changes: 35 additions & 1 deletion src/@lib/equalities/shallowEquals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
export function shallowEquals<T>(objA: T, objB: T): boolean {
return objA === objB;
// 두 객체의 참조 레퍼런스 값이 같은 경우, 또는 기본 타입인데 같은 경우
if (objA === objB) {
return true;
}

// 두 객체 중 하나라도 객체가 아니거나 null인 경우
if (
typeof objA !== "object" ||
objA === null ||
typeof objB !== "object" ||
objB === null
) {
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;
}

// 모든 키에 대해서 얕은 비교 수행
// general하게 사용되는 shallowEquals이므로, Object.prototype.hasOwnProperty를 사용 (call로 매핑해줌으로써, 예외상황을 처리)
for (const key of keysA) {
if (
!Object.prototype.hasOwnProperty.call(objB, key) ||
objA[key] !== objB[key]
Comment on lines +29 to +30

Choose a reason for hiding this comment

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

이렇게 사용하면 타입 오류나지 않나요??

) {
return false;
}
}

return true;
}
18 changes: 15 additions & 3 deletions src/@lib/hocs/memo.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { shallowEquals } from "../equalities";
import { ComponentType } from "react";
import React, { ComponentType, ReactNode } from "react";

export function memo<P extends object>(
Component: ComponentType<P>,
_equals = shallowEquals,
) {
return Component;
// ref에 대한 의문, hooks가 아닌데, ref를 사용하는게 맞는가? -> hooks라는 전제를 깔자.
// TODO : 이거 관련해서 다시 한번 깊게 탐구해보기. Ref가 진짜로 필요한가? 그리고, 만약 클로져로 처리했을 때 리랜더링이 안일어나나?
// let preProps = useRef<P | null>(null);
let preProps: P | null = null;
let memoizedComponent: ReactNode | null = null;

return function (props: P) {
if (preProps === null || !_equals(preProps, props)) {
preProps = props;
// jsx 였으면, 컴포넌트 생성해도 좋았겠지만, 결국 이것도 오버헤드일 것 같기에 createElement로 진행
memoizedComponent = React.createElement(Component, props);
}
return memoizedComponent;
};
}
7 changes: 4 additions & 3 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 */
/* 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;
const memoizedFactory = useMemo(() => factory, _deps);
return memoizedFactory as T;
}
17 changes: 14 additions & 3 deletions src/@lib/hooks/useMemo.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
/* 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();
const prevDeps = useRef<DependencyList | null>(null);
const prevResult = useRef<T | null>(null);

if (
prevResult.current === null ||
prevDeps.current === null ||
!_equals(prevDeps.current, _deps)
) {
prevDeps.current = _deps;
prevResult.current = factory();
}

return prevResult.current;
}
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";

export function useRef<T>(initialValue: T): { current: T } {
// React의 useState를 이용해서 만들어보세요.
return { current: initialValue };
// useRef, useState 모두 컴포넌트의 라이프사이클 동안 값을 유지하는 게 필요함.
// useState는 값의 변동이 있을 때 리랜더링이 일어남. 참조의 경우도 참조값이 변해야 함. 그래서, useState의 기본 기능을 활용하되, 참조값을 업데이트 하지 않는 방식으로 구현.
const [refObject] = useState({ current: initialValue });
return refObject;
}
Loading
Loading