과제 체크포인트
배포 링크
https://taeyeong0814.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 개선
과제 셀프회고
기술적 성장
- useRef, useMemo, useCallback, useDeepMemo, useShallowState, useAutoCallback 등 다양한 커스텀 훅을 직접 구현하며, React의 상태 관리와 메모이제이션의 기본 원리에 대해 공부하여 학습 할 수 있었습니다.
- 현업에서도 타입스크립트를 사용은 하고 있으나 유틸리티 타입 같은 것은 사실 알고 사용하지 않았는데 이 부분에 개념도 알게 되었습니다.
- 과제를 진행하면서 제네릭 에 대한 부분도 많이 접했는데 자바 공부 할 때 들었었고, 아직까지도 정확히 무엇인지 모르지만 이번 과제를 하면서 추후에 블로그 주제로 사용하여 정리가 필요한 상황이지만 "타입을 나중에 결정할 수 있게 해주는 타입 변수" 정도로 습득하고 알아갔습니다.
자랑하고 싶은 코드
- 처음에 구현을 했을 땐 store를 먼저 구독하고 selector과 shallow를 비교했는데 shallow 비교가 적용 된 selector를 getSnapshot에서 바로 사용하게 하면 불필요한 리렌더링을 방지 할 수 있을 것으로 생각되어 변경하였습니다.
export const useStore = <T, S = T>(store: Store<T>, selector: (state: T) => S = defaultSelector<T, S>) => {
// useSyncExternalStore와 useShallowSelector를 사용해서 store의 상태를 구독하고 가져오는 훅을 구현해보세요.
// 내가 짠 코드
// 1. store의 상태 변화를 구독
// const state = useSyncExternalStore(store.subscribe, store.getState);
// 2. selector로 원하는 값 추출 및 shallow 비교
// return useShallowSelector(selector)(state);
// 변경 한 코드
const shallowSelector = useShallowSelector(selector);
//상태가 변하지 않았을 때는 렌더링이 되지 않기 위해 shallowSelector를 사용
return useSyncExternalStore(store.subscribe, () => shallowSelector(store.getState()));
};
- deepEquals 함수를 구현 할 때 두 값을 깊은 비교를 하는데 코드를 작성하고 나니 a를 기준으로만 for 문 처리를 했었는데 그러면
엣지 케이스 (예를 들어
{ a: 1 }vs{ a: 1, b: 2 }) 처럼 A가 B에 포함되는 상황이 생각났습니다. 그래서 아래와 같은 코드를 추가해서 확실하게 비교 처리하였습니다.
// keysB에 있는 모든 키가 keysA에도 있는지도 확인
for (const key of keysB) {
if (!keysA.includes(key)) return false;
}
return true;
}
return false;
개선이 필요하다고 생각하는 코드
- 저는 useRef 함수를 아래와 같이 작성하였습니다. 하지만 코드에 대한 내용을 질문하고 공부하면서 초기값을 객체로 전달하는 것과 함수로 전달하는 것에 대한 차이에 대해 알게 되었습니다. 그래서 이 부분을 불필요하게 계속 객체가 만들어지지 않게 함수로 전달하는 코드로 작성하는 것이 더 권장되는 방법인 부분까지 알게 되었습니다. 즉! 리팩토링이 필요한 코드입니다.
import { useState } from "react";
// useRef 훅은 렌더링 사이에 값을 유지하는 가변 ref 객체를 생성합니다.
export function useRef<T>(initialValue: T): { current: T } {
const [state] = useState({ current: initialValue });
return state;
}
학습 효과 분석
- 제가 이번 과제에서 제일 깊고 배움이 컷던 부분은 메모이제이션, useMemo, useCallback의 개념입니다. 값을 재사용하는 것, 함수를 재사용하는 것을 시작으로 비슷하지만 다른 API 이였습니다. 또한 제일 핵심적이라고 생각이 되었는데 어떤 API인지 어떻게 사용하는지는 이미 다 작성을 해봐서 아는 부분이라 가정하고 넘어가고 이 API들은 성능 최적화를 위해 존재하는 API인데 사실 잘 못 사용하면 오히려 성능이 안 좋아 질 수 있습니다. 무군별하게 사용하면 메모리를 점유하고 있는 메모들이 많을 것입니다.
과제 피드백
좋았던 부분
- 커스텀 훅, 비교 함수, 상태 관리 등 실무에서 자주 쓰이는 패턴을 직접 구현해보며 원리를 깊이 이해할 수 있었습니다.
- md 파일로 구현 과정과 고민, 개선 내역을 정리하면서 스스로의 성장과정을 체계적으로 돌아볼 수 있었습니다.
- 왜 쓰는지 모르고 그냥 쓰고 있던 hooks 을 과제 실습을 통해 단순 이론 혹은 그냥 import 가 아니라 "직접 작성하고 알아가고 배우는" 경험이 많아 만족스러웠습니다. 개인적으로는 1주차, 2주차보다 이번 주차 내용이 더 좋았습니다.
모호하거나 애매했던 부분
- useRef, useMemo 등 훅을 그냥 직접 구현을 하려고 하니 어려웠는데 이 부분은 사실 참고 자료를 보고 난 뒤 진행하라고 하셨기 때문에 제 시작 방식의 문제라고 판단 됩니다.
- 이번 과제에서는 모호하거나 애매하다고 느낀 부분은 없습니다.
학습 갈무리
리액트의 렌더링이 어떻게 이루어지는지 정리해주세요.
- React는 상태나 props가 변경되면 가상 DOM에서 변경점을 계산하고, 실제 DOM에는 꼭 필요한 부분만 최소한으로 반영합니다. 이 과정에서 불필요한 렌더링을 막기 위해 memoization, 비교 함수, 커스텀 훅 등이 적극적으로 활용됩니다.
- 즉, "변경이 감지된 부분만 다시 렌더링"는 것이 React 렌더링의 핵심입니다.
메모이제이션에 대한 나의 생각을 적어주세요.
- 메모이제이션은 불필요한 연산과 렌더링을 줄여 성능을 최적화하는 데 필수적인 기법입니다. 하지만 무분별하게 사용하면 코드가 복잡해지고, 오히려 관리가 어려워질 수 있습니다. "진짜로 값이 자주 바뀌지 않는 계산"에만 신중하게 적용하는 것이 중요하다고 생각합니다.
컨텍스트와 상태관리에 대한 나의 생각을 적어주세요.
- Context와 커스텀 훅을 활용한 상태 관리는 전역 상태를 효율적으로 공유하고, 컴포넌트 간 결합도를 낮추는 데 매우 유용합니다. 하지만 Context를 남용하면 모든 컴포넌트가 불필요하게 리렌더링 될 수 있으므로, 꼭 필요한 범위에서만 사용하는 것이 좋다고 느꼈습니다. 상태 관리의 핵심은 "필요한 곳에만, 필요한 만큼" 데이터를 전달하는 것임을 다시 한 번 깨달았습니다.
리뷰 받고 싶은 내용
React 19 릴리즈 내용을 틈틈히 읽고 있었는데 거기서 자동 메모이제이션(Automatic Memoization) 을 보았던 기억이 있습니다. https://react.dev/blog/2025/04/21/react-compiler-rc
그걸 React Compiler 로 부르며 빌드 타임 도구라는 설명을 보았었는데 현재 찾아보니 RC 버전으로 업데이트가 되었더라고요.
Docs 문서나 글을 통해 제가 직접 Auto-Memoization, Compiler... 그래서 추후에 이게 반영이 된다면 기존과 어떤 차이점이 있는데? 를 파악해야 하는데 현재는 React Compiler로 useMemeo, useCallback 안 써도 되겠다. 싶은 생각 정도까지 했습니다.
이와 관련한 내용들의 정보나 내용 설명이 궁금합니다! (사실 결론적으로 "네, 이젠 안 쓸 겁니다." 혹은 "하지만 그럼에도 이런 케이스 같은 경우엔 써야 할 것으로 보여요." 등과 같은 결론적 답변이 더 궁금합니다.)
추가적으로 React docs나 그 외 다른 어떤 개발적인 내용을 공부 할 때 어떤 식으로 확인하고 공부하는 것이 좋을까요? 예를 들어 저는 지금 store 의 개념에 대해 잘 모르는 상황이라 useStore 를 검색하면 useStorage 가 같이 나오며 subscribe, dispatch, Redux, Zustand.. 등등 단어가 꼬리의 꼬리를 물며 나오는데 다 읽고 보다 보면 내가 어떤 것을 그래서 알려고 한 것 이지? 정리가 안되는 상황이 많이 있습니다. 현업에서 릴리즈도 끊어가야 늘어지지 않는다는 것처럼 공부 내용도 끊어가며 알아가야 하는데 결국 연관이 되어 있다 보니 어렵습니다.
과제 피드백
태영님 이번 한 주 알차게 보내신게 회고에서 드러나네요 ㅎㅎ 과제는 당연히 잘 작성해주셨고 정리한 내용도 각각 문서로 만들어서 정리해주셨네요. (요거는 직접 작성해주셨으려나요?) 명확하게 잘 정리해주셨고 나중에 이 과제를 다시 봤을때에도 귀중한 자료가 되지 않을까 싶습니다 :+1
질문 주셨던 내용 답변드려보면요!
Docs 문서나 글을 통해 제가 직접 Auto-Memoization, Compiler... 그래서 추후에 이게 반영이 된다면 기존과 어떤 차이점이 있는데? 를 파악해야 하는데 현재는 React Compiler로 useMemeo, useCallback 안 써도 되겠다. 싶은 생각 정도까지 했습니다.
이 부분에 대해서는 꾸준히 저도 트래킹하고 있는데요. 결론적인 답변을 원하셨으니 말해보면.. 지금의 수준에서는 모든걸 대체하기에는 어려워보여요. 다만, 초창기에 비해서는 최적화수준이 많이 올라왔고 처음에 제가 이걸 접했을 때는 못쓸거라고 생각했지만 지금은 대체가 될 수도 있겠다라는 생각이였거든요. 비슷한 라이브러리에서는 이미 해당 기능을 제공하고 있고 리액트 측에서도 이 기능을 완성시키겠다고 공언을 이미 했고..라이브러리 시장에서 리액트가 안좋다! 라는 근거로 가장 많이 쓰이는 부분이기 때문에 어떻게든 되게 만들지 않을까 라는 생각이긴해요.
다만, 지금처럼 가야한다면 명확한 메모이제이션 사용법에 대해 이해하고 각각 사용해야 하며 컴파일러는 부수적인 도움을 주는 역할이다라고 봐야하지 않을까 싶습니다. RC인 만큼 계속 트래킹을 해야할거같아요!
추가적으로 React docs나 그 외 다른 어떤 개발적인 내용을 공부 할 때 어떤 식으로 확인하고 공부하는 것이 좋을까요?
이 부분에 대해서는
- 궁금한 것을 기준으로 명확한 공부의 범위 또는 주제를 단일로 지정하세요.
- 그리고 그걸 도달하기 위해 가는 과정을 본격적으로 공부하기전에 추측하고 적어보세요.
- 공부를 하는 과정에서 정말 도움이 되는 부분이라면 정리해둔 목차에 추가를 하고, 아니다 싶으면 목차를 제거해보세요. 다만, 이 목차를 추가하는 시점에 처음 지정한 범위를 벗어난다 싶으면 키워드만 적어두고 넘어가세요. 이 키워드는 다음 공부에서 주제가 될 수 있겟죠?
- 그리고 이 목차를 채우면서 주제에 대한 결론을 낼 수 있으면 마무리하세요
말씀해주신것처럼 새로운 기술에 대해 너무 자주 나오고, 공부할 것도 너무 많죠. 다만 그냥 꼬리 물기로 공부하다보면 내가 궁금해 했던 것들을 이해하지 못하게 되고 확장적인 생각을 갖는데에도 좋지 않은것 같아요. 저는 저런식으로 공부를 많이 해서 도움을 받았는데 한번 해보고 다음에 함께 이야기 나눠보면 좋겠네요!
고생하셨고 다음주도 화이팅입니다!