Skip to content

[1팀 이의찬] Chapter 1-3. React, Beyond the Basics#36

Open
Legitgoons wants to merge 21 commits intohanghae-plus:mainfrom
Legitgoons:main
Open

[1팀 이의찬] Chapter 1-3. React, Beyond the Basics#36
Legitgoons wants to merge 21 commits intohanghae-plus:mainfrom
Legitgoons:main

Conversation

@Legitgoons
Copy link

@Legitgoons Legitgoons commented Jul 21, 2025

과제 체크포인트

배포 링크

https://legitgoons.github.io/front_6th_chapter1-3

기본과제

equalities

  • shallowEquals 구현 완료
  • deepEquals 구현 완료

hooks

  • useRef 구현 완료
  • useMemo 구현 완료
  • useCallback 구현 완료
  • useDeepMemo 구현 완료
  • useShallowState 구현 완료
  • useAutoCallback 구현 완료

High Order Components

  • memo 구현 완료
  • deepMemo 구현 완료

심화 과제

hooks

  • createObserver를 useSyncExternalStore에 사용하기 적합한 코드로 개선
  • useShallowSelector 구현
  • useStore 구현
  • useRouter 구현
  • useStorage 구현

context

  • ToastContext, ModalContext 개선

과제 셀프회고

  • 지난 과제들에서는 ai 사용을 지양하고, 가능한 직접 코드를 작성하려고 해왔습니다
    • 하지만 잘 되진 않았고 막판에 그냥 막 쓰긴 했습니다.
  • 그래서 이번에는 ai를 처음부터 적극적으로 사용해서 구현해보았습니다.
    • 당연히 구현 속도가 매우 빨랐지만 스스로 코드를 제대로 이해한지는 확신하기 어려웠고, 지금도 코드를 복기하면서 학습중입니다.
    • 그래도 전반적으로는 코드 전체를 훑어보면서 학습할 수 있다는 부분에서 지난 과제들보다는 스스로 만족도가 높았습니다.
    • 산들님이 ~~~를 구현해보려고 하는데 가이드를 해줘. 처음부터 정답을 알려주지 말고 내가 충분히 학습하면서 정답에 가까워질 수 있도록 질문을 하면서 정답까지 이끌어줘.라는 방식의 프롬프트를 사용해보라고 조언해주셔서 다음 과제부터는 이렇게 해보려고 합니다.

기술적 성장

  • 리액트가 얕은 비교를 주로 사용하는 이유
  • 실제 Hook의 구현 원리들

자랑하고 싶은 코드

커밋 링크

feat: 타입 가드를 사용해 isObject 구현
refactor: equals/util에 helper함수 compareObject 구현

  • equals를 구현하면서 하나의 함수에 많은 로직들이 얽혀있다는 것을 느꼈고, 헬퍼함수들을 사용해서 선언적으로 구현해보았습니다.
  • 주요 고민 : return keysA.every((key) => compareFn(a[key], b[key])); 부분에서 로직이 달라 어떻게 해야할지 고민했습니다.
  1. compareObjectDeep / compareObjectShallow를 분리하는 것
  2. 비교함수를 매개변수로 추가하는 것
  3. 비교 수행 로직을 compareObject에 넣어두고, deepEquals / shallowEquals 를 매개변수로 받아서 이에 따라 적용
  • 위 세 가지 방안을 생각해보았는데, 우선 3번의 경우에는 순환 의존성 문제가 있어서 배제하였고, 1번의 경우에는 명시적으로 분리해주는 것은 좋지만 중복되는 코드량이 많아 2번을 선택하였습니다.
export function isObject(value?: unknown): value is Record<string, unknown> {
  return value !== null && typeof value === "object";
}

export function compareObject(
  a: Record<string, unknown>,
  b: Record<string, unknown>,
  compareFn: (a: unknown, b: unknown) => boolean = (a, b) => a === b,
): boolean {
  // 객체의 키 개수가 다른 경우 처리
  const keysA = Object.keys(a);
  const keysB = Object.keys(b);
  if (keysA.length !== keysB.length) return false;

  // 모든 키에 대해 비교 수행
  return keysA.every((key) => compareFn(a[key], b[key]));
}

개선이 필요하다고 생각하는 코드

  • 메모이제이션에 대해서 충분히 학습하지 못하고 넘어가서 아쉽습니다.
  • Toast Provider 부분도 깊게 고민하지 못하고, 우선 동작하는대로 ai에게 맡기고 넘겼는데 이 부분도 추가적으로 학습하고 싶습니다.
    • 위 두 부분을 학습해서 기술적 성장으로 옮기고 싶습니다...!!

학습 효과 분석

과제 피드백

학습 갈무리

리액트의 렌더링이 어떻게 이루어지는지 정리해주세요.

  • React의 랜더링 과정
KakaoTalk_Photo_2025-07-16-16-03-34 - 이미지 출처 : 7팀의 병준님

메모이제이션에 대한 나의 생각을 적어주세요.

  • 아직 학습이 모자란 부분이라, 추후 정리해서 작성하겠습니다

컨텍스트와 상태관리에 대한 나의 생각을 적어주세요.

  • 컨텍스트와 상태관리를 사용했을 때의 장점과 단점은 무엇일까?
    • 장점
      • 깊은 컴포넌트 구조에서 상태 공유가 용이해진다.
      • 선언적으로 컴포넌트를 명시함으로써 가독성을 높일 수 있음
    • 단점
      • Context 값이 변경되면 해당 Context를 구독하는 모든 컴포넌트가 리렌더링
      • Context가 많아지게 된다면 Provider hell에 빠지게 됨

리뷰 받고 싶은 내용

Comment on lines +1 to +29
/**
* 주어진 값이 객체인지 확인하는 함수
* @reference https://github.com/toss/es-toolkit/blob/main/src/compat/predicate/isObject.ts
* @param {unknown} value - 객체인지 확인할 값
* @returns {value is Record<string, unknown>} 객체인지 확인 결과
*/
export function isObject(value?: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object";
}

/**
* 두 객체를 비교하는 함수
* @param a 비교할 첫 번째 객체
* @param b 비교할 두 번째 객체
* @returns 두 객체가 같은지 확인 결과
*/
export function compareObject(
a: Record<string, unknown>,
b: Record<string, unknown>,
compareFn: (a: unknown, b: unknown) => boolean = (a, b) => a === b,
): boolean {
// 객체의 키 개수가 다른 경우 처리
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;

// 모든 키에 대해 비교 수행
return keysA.every((key) => compareFn(a[key], b[key]));
}

Choose a reason for hiding this comment

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

오 utils 함수로 분리 좋습니다!

Choose a reason for hiding this comment

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

compareObject 함수명만 봤을 때 상세 동작을 상상하기가 어려운 것 같아요! 참조값을 비교하는지, 키값을 비교하는지 더 명확하게 알 수 있으면 좋을 것 같아요!! compareObject로 분리하는 것 좋네요 반복되는 로직이었는데! 👍🏻

// useSyncExternalStore 에서 활용할 수 있도록 subscribe 함수를 수정합니다.
const subscribe = (fn: Listener) => {
listeners.add(fn);
return () => {

Choose a reason for hiding this comment

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

subscribe에서 반환해주는 함수 로직이 unsubscribe랑 동일하니까 unsubscribe를 그대로 반환해주면 될 것 같아요!

// 직접 작성한 useRef를 통해서 만들어보세요.
return factory();
// 1. 이전 의존성과 결과를 저장할 ref 생성
const ref = useRef<{ deps: DependencyList; result: T } | null>(null);

Choose a reason for hiding this comment

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

주석이 없어도 이 ref값이 잘 이해되도록 구체적인 변수명으로 해주면 더 좋을 것 같아요!

return useSyncExternalStore(
router.subscribe,
() => shallowSelector(router),
() => shallowSelector(router),

Choose a reason for hiding this comment

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

getServerSnapshot 파라미터는 useRouter 파라미터에 옵션으로 넣어줘도 될 것 같아요

export const useRouter = <T extends RouterInstance<AnyFunction>, S>(
  router: T,
  selector = defaultSelector<T, S>,
  getServerSnapshot?: () => S, // 옵셔널 파라미터
) => {
  // useSyncExternalStore를 사용하여 router의 상태를 구독하고 가져오는 훅을 구현합니다.
  const shallowSelector = useShallowSelector(selector);
  return shallowSelector(router);
  return useSyncExternalStore(
    router.subscribe,
    () => shallowSelector(router),
    getServerSnapshot // 변경

// 생략

if (prevState === undefined) return initialValue;

// 함수형 업데이트인지 확인
const nextValue = typeof action === "function" ? (action as (prev: T) => T)(prevState) : action;

Choose a reason for hiding this comment

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

오 함수형 업데이트도 확인하는 로직이 있네요 저는 생각 못 한 케이스네요 굿!! 👍🏻

export const useStorage = <T>(storage: Storage<T>) => {
// useSyncExternalStore를 사용해서 storage의 상태를 구독하고 가져오는 훅을 구현해보세요.
return storage.get();
return useSyncExternalStore(storage.subscribe, storage.get, storage.get);

Choose a reason for hiding this comment

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

storage를 사용하는 훅이니까 세번째 서버 스냅샷 가져오는 함수 파라미터는 안 넣어줘도 되지 않을까요? 대부분 storage는 서버랑 관련이 없는 클라이언트 스토리지일테니까요!

return useSyncExternalStore(
store.subscribe,
() => shallowSelector(store.getState()),
() => shallowSelector(store.getState()),

Choose a reason for hiding this comment

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

useStorage와 동일한 의견입니다!

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