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

Conversation

pitangland
Copy link

@pitangland pitangland commented Jan 2, 2025

과제 체크포인트

기본과제

  • shallowEquals 구현 완료
  • deepEquals 구현 완료
  • memo 구현 완료
  • deepMemo 구현 완료
  • useRef 구현 완료
  • useMemo 구현 완료
  • useDeepMemo 구현 완료
  • useCallback 구현 완료

심화 과제

  • 기본과제에서 작성한 hook을 이용하여 렌더링 최적화를 진행하였다.
  • Context 코드를 개선하여 렌더링을 최소화하였다.

과제 셀프회고

리액트의 기본 훅을 직접 구현하며 훅의 내부동작과 장점이 무엇인지 어떻게 사용해야하는지 알게되었다. 이전에 사용할 때는 주의점이나 올바른 활용방법을 충분히 알지 못한 채 사용했었지만, 이번 과제를 통해 기본 훅과 커스텀 훅에 대해 알게되었고, 설계 방향성을 고민해보는 시간이 되었다! 앞으로 기본적인 훅과 커스텀 훅을 활용하여 효율적인 코드를 작성할 수 있을 것 같다.

기술적 성장

훅에 대한 개념이 부족하다 생각해 기본 훅에 대해 집중적으로 학습했다. 그리고 사실 학습자료와 같은 내용이지만 한번 더 정리해보았다..ㅎ

React Hooks는?
함수형 컴포넌트에서 상태와 생명주기 기능을 사용할 수 있게 해주는 함수들.

  • 함수형 컴포넌트에서 상태 관리 기능
  • 생명주기 메서드 대체
  • 로직의 재사용성 향상
  • 관심사의 분리를 통한 코드 구조화

    ⇒ 따라서 복잡한 로직을 가진 컴포넌트도 구조화하고 관리할 수 있다!

(개념도 모르고 쓰라길래 여기저기 사용하던) useState와 useEffect의 개념과 주의할 점은 다음과 같다.

  • useState

    함수형 컴포넌트에서 상태를 관리할 수 있게 해준다.

    • 컴포넌트에 지역 상태를 추가할 수 있음
    • 함수형 컴포넌트를 다시 렌더링하여 상태를 업데이트함
    • 배열 구조 분해를 통해 현재 상태값과 상태를 업데이트하는 함수를 제공

    주의할 점으로는

    • 상태 업데이트는 비동기적 (정확히는 스냅샷)
    • 객체나 배열을 상태로 사용할 때는 항상 새로운 참조를 생성
    • 복잡한 상태 로직의 경우 useReducer를 고려할 수 있음
  • useEffect

    데이터 가져오기, 구독 설정, 수동으로 DOM 조작하기 등 컴포넌트의 주요 렌더링 작업 외의 작업들

    • 컴포넌트가 렌더링 된 후 실행
    • 기본적으로 모든 렌더링 이후에 실행, but 최적화를 통해 특정 값들이 변경되었을 때만 실행되도록 설정할 수 있음

    주의할 점으로는

    • 의존성 배열을 올바르게 설정, 빈 매열은 컴포넌트 마운트 시에만 실행됨을 의미
    • 정리함수를 반환하여 필요한 정리 작업을 수행
    • 무한 루프를 방지하기 위해 의존성 배열을 신중히 설정
    • 비동기 작업 수행 시 경쟁 상태를 주의


      ⇒ 함수형 컴포넌트에서 생명주기와 관련된 작업을 수행할 수 있게 해주는 강력한 도구

직접 구현해본 useRef, useMemo, useCallback의 개념과 주의할 점은 다음과 같다.

  • useRef

    렌더링에 필요하지 않은 변경 가능한 값을 저장하는데 사용되는 Hook

    • 반환된 ref 객체는 컴포넌트의 전체 생명주기 동안 유지
    • ref 객체의 .current 속성을 변경해도 리렌더링이 트리거되지 않음
    • DOM 요소에 접근하거나 이전 상태를 저장하는 등 다양한 용도로 사용될 수 있음

    주의할 점으로는

    • ref의 .current 속성 변경은 리렌더링을 트리거하지 않음

      따라서, 렌더링과 관련된 값을 저장하는 데는 적합하지 않음
    • useRef로 생성된 ref 객체는 컴포넌트가 언마운트될 때까지 유지

      이는 메모리 누수를 일으킬 수 있으므로 필요하지 않은 경우 정리
    • DOM 조작을 직접 수행할 때는 React의 선언적 패러다임을 벗어나므로 주의해서 사용해야 함
  • useMemo

    계산 비용이 큰 함수의 결과값을 메모이제이션하는 Hook

    • 의존성 배열의 값이 변경되지 않는 한, 이전에 계산된 값을 재사용
    • 렌더링 중에 실행되는 비용이 큰 계산을 최적화하는 데 유용
    • 값뿐만 아니라 객체나 배열도 메모이제이션할 수 있음

    주의할 점으로는

    • 모든 값에 useMemo를 사용하는 것은 메모리 사용량을 증가시키고 초기 렌더링 성능을 저하시킬 수 있음
    • 의존성 배열을 올바르게 설정해야 함

      잘못된 의존성은 최신 값을 사용하지 못하는 버그를 유발할 수 있음
    • 성능 최적화를 위한 도구, 의미론적 보장을 위해 사용X
  • useCallback

    메모이제이션된 콜백 함수를 반환하는 Hook

    • 의존성 배열이 변경되지 않는 한, 동일한 함수 참조를 유지함
    • 주로 자식 컴포넌트에 콜백을 전달할 때 사용
    • React.memo와 함께 사용하여 컴포넌트의 불필요한 리렌더링 방지

    주의할 점으로는

    • 모든 함수에 useCallback을 사용하는 것은 오히려 성능 저하, 필요한 경우에만 사용
    • 의존성 배열을 올바르게 설정, 잘못된 의존성은 버그를 유발
    • 자식 컴포넌트에 함수를 prop으로 전달할 때 유용

코드 품질

App.tsx는 관심사가 모두 모여있는 구조로 리팩토링이 필요할 것 같다. 현재 과제에서의 테스트 코드를 통과하는 포인트는 아니었지만 나중에 시간을 내서 관심사 분리와 컴포넌트 계층화 작업을 진행해야겠다는 목표를 세우게 되었다. (설날, 쉬는 주차 Chapter1에 대해 전체적으로 리팩토링을 진행해보아야겠다)

학습 효과 분석

과제에서 나오지 않은 기본 React Hooks들도 당!연!히! 살펴볼 필요가 있다고 느꼈다.

훅의 개념을 이해하는 데 시간이 오래 걸려 리팩토링을 진행하지 못한 점은 아쉬웠지만, 이번 과제를 통해 React가 왜 프레임워크의 대세가 되었는지, 그리고 그 장점이 무엇인지 깊이 알아가는 시간이었다. 단순히 사용만 했던 과거와 비교해 이제는 React의 원리와 효율성에 대해 더 명확히 이해하게 되어 의미 있는 성장이었다!

과제 피드백

훅을 직접 구현하고, 이를 활용하여 성능 최적화를 진행하며 기본과 심화 과제가 자연스럽게 연결되어있어 해결하고 이해하기 수월했다. 특히, 리액트의 내장 훅과 내가 직접 구현한 코드를 비교하며, 리액트가 제공하는 기본 훅들의 장점을 알았지만 동시에 이러한 훅들을 커스텀 훅으로도 충분히 구현하고 활용해볼 수 있겠다는 생각이 들었다.

그리고 조금 스스로 조금 아쉬운 점은 타입스크립트에 대한 이해도였다.. 이렇게 느낀 이유는 as T 와 같은 방식을 사용하여 해결했다는 점에서 타입스크립트를 보다 깊이 이해하고 활용하지 못해 아쉬웠다. 왜 특정 타입이 그렇게 결정되는 것인지 null로 들어올 수 있는 가능성에 대한 처리가 왜 필요한지를 명확히 이해하지 못하였다. 타입스크립트의 타입을 결정하는 시스템은 강력한 도구이지만, 그만큼 활용하고 충분히 이해하지 못해 아쉽고, 안정적이고 예측 가능한 코드를 작성할 수 있도록 노력해야겠다.

리뷰 받고 싶은 내용

...useMemo.ts 

  // 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;
타입 안전성 관련
-  ref.current?.value as T에서 이것은 잘못된 값이 반환되어도 컴파일러가 경고하지 않게 만들 수 있다고 찾아보았습니다. 따라, as T를 사용하는 방식이 안전한지에 대해 검토받고싶습니다.

Copy link

@ywkim95 ywkim95 left a comment

Choose a reason for hiding this comment

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

코드 너무 잘 봤습니다!
저와는 또 다르게 접근하시는 걸 보고 좋은 인사이트를 얻어간다고 생각이 듭니다!
이번 주차도 고생 많으셨고, 이후 주차도 화이팅입니다!

// 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} /> 로 적용했거든요! 다른 분들도 이렇게 적용하시더라구요! 저도 이 방식으로 적용해볼 걸 그랬나봐요!

Comment on lines +13 to +16
const ref = useRef<{ deps: DependencyList | null; value: T | null }>({
deps: null,
value: null,
});
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를 각각 선언하고 각각 값을 할당해주는 방식으로 진행했어요! 이 부분도 오히려 이렇게 하나로 하는게 더 좋아보이네요!

Comment on lines +32 to 47
interface ThemeContextType {
theme: string;
toggleTheme: () => void;
}

interface UserConTextType {
user: User | null;
login: (email: string, password: string) => void;
logout: () => void;
}

interface NotificationContextType {
notifications: Notification[];
addNotification: (message: string, type: Notification["type"]) => void;
removeNotification: (id: number) => void;
}
Copy link

Choose a reason for hiding this comment

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

저는 이 방법이 제일 정석적인 방법이라고 생각이 들어요! 그래도 하나의 방법이 더 있긴한데 어떤 방법이나면
Pick이라는 것을 활용해보는 건데요! 외부 라이브러리의 타입이나 팀원과 공유하는 공통코드를 활용할 때 사용하면 좋을 듯 합니다! 생각해보니 DuckTyping 이라는 것도 있네요!
아래는 예시 코드도 보여드릴게요!

// 기존 코드 그대로 활용
interface AppContextType {
  theme: string;
  toggleTheme: () => void;
  user: User | null;
  login: (email: string, password: string) => void;
  logout: () => void;
  notifications: Notification[];
  addNotification: (message: string, type: Notification["type"]) => void;
  removeNotification: (id: number) => void;
}
// Pick 타입 사용으로 따로 코드를 추가로 선언할 필요 없이 사용 가능!
const ThemeContext = createContext<
  Pick<AppContextType, "theme" | "toggleTheme"> | null
>(null);

Copy link
Author

Choose a reason for hiding this comment

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

오 이런방법이 있었군여,, 나중에 협업할 때 활용해보면 좋을 것 같아요! 감사합니다~!

Comment on lines +6 to +13
if (
typeof objA !== "object" ||
objA === null ||
typeof objB !== "object" ||
objB === null
) {
return false;
}
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를 찾는 방법이네요!
저와는 다른 방식으로 접근하셔서 좋은 인사이트를 얻었습니다!

Comment on lines +24 to +25
!Object.prototype.hasOwnProperty.call(objB, key) ||
objA[key] !== objB[key]
Copy link

Choose a reason for hiding this comment

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

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

Comment on lines +5 to +6
useCallback,
useMemo,
Copy link

Choose a reason for hiding this comment

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

하핫 코드 에디터에서 자동으로 import 해줬나보네요!😂 직접 구현하신 훅으로 적용해보는 것도 좋을 것 같아요!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants