-
Notifications
You must be signed in to change notification settings - Fork 75
[7팀 이지원] Chapter 1-3 React, Beyond the Basics #53
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
base: main
Are you sure you want to change the base?
Conversation
keyonnaise
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지원님 과제 고생 많으셨습니다. 언제나 늦게까지 열심히 하시는 모습에 저도 많은 자극을 받고 있어요!! 다음 과제도 힘내서 잘 풀어내시기를 바랍니다.
이 글들은 Context API 최적화 기법이랑 Redix UI의 undefined safe한 context 생성 함수 그리고 useCallback 한계를 고민하다가 생긴 usePreservedCallback에 관한 글입니다! 과제 정리하시면서 참고 삼아 보시면 좋을 것 같아 남겨놓습니다.
참고자료
| if (typeof objA === "object" && typeof objB === "object") { | ||
| const objATyped = objA as Record<string, unknown>; // 타입... 선언을 위한..(귀찮네요) | ||
| const objBTyped = objB as Record<string, unknown>; | ||
|
|
||
| const keysA = Object.keys(objATyped); | ||
| const keysB = Object.keys(objBTyped); | ||
|
|
||
| // 키 개수 비교 | ||
| if (keysA.length !== keysB.length) return false; | ||
|
|
||
| // 키 철저하게 비교.. | ||
| for (const key of keysA) { | ||
| // 키 존재 여부 확인 | ||
| if (!Object.prototype.hasOwnProperty.call(objBTyped, key)) return false; | ||
|
|
||
| if (!deepEquals(objATyped[key], objBTyped[key])) return false; | ||
| } | ||
|
|
||
| return true; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
33번째 줄에 objA as Record<string, unknown>를 사용하셨는데 const keysA = Object.keys(obj) as (keyof T)[]를 사용하면 좀 더 간결한 타입스크립트 코드 작성이 가능하답니다..!
// 예시 코드
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 (objA[key] !== objB[key]) return false;
// 키 존재 여부 확인
if (!Object.prototype.hasOwnProperty.call(objB, key)) return false;
if (!deepEquals(objA[key], objB[key])) return false;
}
return true;
| // 키 철저하게 비교.. | ||
| for (const key of keysA) { | ||
| // 키 존재 여부 확인 | ||
| if (!Object.prototype.hasOwnProperty.call(objBTyped, key)) return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Object.prototype.hasOwnProperty.call()을 사용하신게 눈에 띄어요! ES2022에서는 hasOwn()을 사용할 수도 있답니다. 하지만 사용할 수 없을 때도 있으니 Object.prototype.hasOwnProperty.call() 부분을 따로 유틸함수로 빼서 사용해보셔도 좋을 것 같아요!
이 글을 읽어 보셨는지 모르겠지만 사용하신 Object.prototype.hasOwnProperty.call()에 관한 글입니다. 참고하시면 좋을 것 같아서 남겨놓습니다.
| callback: T, | ||
| deps: DependencyList, | ||
| equals = shallowEquals |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오... useCallback()에 equals를 추가하신건 깊은 비교 처리를 위해서일까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쓰레기코드네요..당장 삭제
| // React의 useState를 이용해서 만들어보세요. | ||
| return { current: initialValue }; | ||
| const [ref] = useState<{ current: T }>(() => { | ||
| return { current: initialValue }; | ||
| }); | ||
|
|
||
| return ref; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소한 부분이지만 return useState(() => ({ current: initialValue }))[0] 처럼 한 줄로 작성할 수도 있어요!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CombineProvider로 App에서 사용되는 Context 들을 하나로 묶으셨네요. 저도 Context Hell에 대해서 고민이 많았어요. 남겨드리는 링크와 같은 방법도 있으니 관심이 있으시다면 한 번 참고해보시는 것도 좋을 것 같아요.
| /** | ||
| * 동등성 비교 함수 | ||
| * 얕은 비교 수행 | ||
| * 중첩된 객체나 배열은 참조 비교 | ||
| * | ||
| * @template T - 비교할 값들 타입 | ||
| * @param objA 1 비교 | ||
| * @param objB 2 비교 | ||
| * @returns 얕게 동등하면 true, 아니면 false | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자세하게 주석 써주셔서 보기 편하네요
| export function deepEquals<T>(objA: T, objB: T): boolean { | ||
| return objA === objB; | ||
| // 기본 타입 비교 (number, string, boolean, null, undefined) | ||
| if (Object.is(objA, objB)) return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엇 넵 참조 동일성만 비교합니다~
| const shouldRender = | ||
| propsRef.current === null || | ||
| !equals(propsRef.current, props) || | ||
| Object.keys(propsRef.current).length !== Object.keys(props).length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
별도로 렌더링 필요한 값을 할당해주니까 더 명확해지네요.
배포 링크
https://e-jiwon.github.io/front_5th_chapter1-3/
과제 체크포인트
기본과제
심화 과제
과제 셀프회고 : )
항상 유익했지만 이번에는 102381배는 유익한 느낌이 들었습니다.
당장 실무에 적용해보고 싶은 느낌..! (백수라서 적용할 실무가 없는게 안타깝습니다.)
기술적 성장
참조 동등성
React의 성능 최적화를 이해하기 위해서는 먼저 '참조 동등성(Referential Equality)'이라는 개념을 확실히 이해해야 하는 것을 알았습니다.
제가 Hooks를 구현하면서 가장 중요하게 느꼈던 부분입니다.
===연산자나Object.is()를 통해 참조 동등성을 확인할 수 있었습니다.이 개념이 중요한 이유는 React가 컴포넌트 리렌더링을 결정할 때 props와 state의 참조 동등성을 확인하기 때문
즉, 객체나 배열의 내용이 동일하더라도 참조가 다르면 React는 이를 변경된 것으로 간주하고 리렌더링을 발생시키는 것을 학습했습니다.
useRef 구현으로 배우는 불변성 관리
값이 변경되어도 리렌더링을 발생시키지 않는 특별한 Hook
ref.current = '새 값'과 같이 값을 업데이트하면, 객체의 속성만 변경될 뿐 객체 자체의 참조는 변경되지 않습니다.따라서 React는 이 변화를 감지하지 못하고 리렌더링을 발생시키지 않습니다.
여기서 얻은 중요한 인사이트
얕은 비교와 깊은 비교: useMemo와 useCallback의 핵심
useMemo와 useCallback을 구현하면서 가장 중요했던 부분은 의존성 배열의 비교 방식이라고 생각합니다.
기본적으로 React는 얕은 비교를 합니다.
얕은 비교 코드 구현
얕은 비교는 객체의 최상위 속성들만 비교합니다.
객체나 배열 같은 중첩된 참조 타입은 참조 동등성만 확인합니다.
깊은 비교 코드 구현
얕은 비교 vs 깊은 비교 언제 써야 할까?
useMemo 구현으로 배우는 메모이제이션
useMemo는 계산 비용이 많이 드는 값을
메모이제이션(캐싱)하여 불필요한 재계산을 방지합니다.
useCallback 구현으로 배우는 함수 메모이제이션
함수 자체를 메모이제이션하기 위한 Hook.
구현 자체는 useMemo와 매우 유사합니다.
useCallback의 실제 활용
useCallback은 자식 컴포넌트에 전달되는 이벤트 핸들러를 메모이제이션할 때 특히 유용했습니다.
React.memo와 함께 사용할 때 그 진가를 발휘한다고 하여 내용을 정리해보았습니다.
useCallback을 사용하지 않으면, 부모 컴포넌트가 리렌더링될 때마다 새로운 함수가 생성되어 자식 컴포넌트로 전달됩니다.
이로 인해 React.memo의 효과가 사라지고, 자식 컴포넌트가 불필요하게 리렌더링되었습니다.
React.memo 구현으로 배우는 HOC(고차 컴포넌트)
React.memo는 컴포넌트를 메모이제이션하기 위한 HOC(Higher-Order Fuctions)입니다.
이부분은 읽고 또 읽어도 너무 어려웠는데요,, 구현하면서 이해하려고 노력했습니다!
HOC 관점에서 설명하자면, memo는 ((컴포넌트))를 인자로 받아서 -> 최적화된 ((컴포넌트))를 생성하여 반환합니다.
-> 컴포넌트를 인자로 받고, 컴포넌트를 생성하여 반환한다고 하여 HOC로 이해했습니다!
최적화 로직 자체를 모듈화해서 재사용한다는 장점이 있고, 내부적으로 props를 비교 로직 처리하여 로직 자체는 숨긴다는 장점을 가지고 있는 것 같습니다!
React.memo의 최적화 전략
컴포넌트 최적화 전략: 렌더링 최소화하기
과제를 통해 얻은 가장 큰 인사이트는 컴포넌트 최적화에 관한 것이었습니다.
컴포넌트 분할 전략
2. 상태 설계 및 Props 인터페이스
이 부분은 타팀 멘토링 시간에 얻어간 인사이트입니다!
3.불변성 관리
불변성을 철저히 지키는 것은 React 변경 감지 매커니즘이 올바르게 작동하는 핵심 !
성능 최적화 시 주의점 : 더 이상은 과최적화
이 부분은 문득 ? 메모이제이션 자체에도 비용이 들지 않을까 해서 정리해본 내용입니다.
구현을 통해 배운 중요한 교훈은 모든 것을 메모이제이션하는 것이 항상 좋은 것은 아니라는 점입니다.
useMemo 주의점
아래의 경우에만 useMemo를 사용하는 것이 적절하다고 판단됩니다.
�관련하여 오버헤드 실험하신 분의 인사이트 남기겠습니다.
Zustand와 같은 상태 관리 라이브러리의 메모이제이션 접근법
코치님이 첨부해주신 부분인데요, 같이 정리합니다.
코드 품질
정리하면서 생각해보니 useCallback 부분을 useMemo를 리턴해도 괜찮았을 것 같다는 생각이 듭니다.
또, 깊은 비교에 대한 원리는 이해한 것 같으나, 실제로 어떤식으로 사용할지 가늠이 잡히지 않습니다.
과제를 진행하면서 모르는 부분을 더 챙기면서 했으면 좋았을 것 같다 생각이 듭니다
학습 효과 분석
React의 성능 최적화 Hooks를 직접 구현해보면서, 단순히 API를 사용하는 것을 넘어 그 내부 작동 원리와 최적화 전략에 대한 깊은 이해를 얻을 수 있었습니다.
특히 참조 동등성, 얕은 비교와 깊은 비교의 차이, 그리고 메모이제이션의 비용과 이점을 명확히 이해하는 것이 중요함을 배웠습니다.
실무에서는 이런 부분을 유념해서 렌더링을 최소화할 수 있을 것 같다는 생각이 들어서 자신감이 생기는 것 같습니다 : )
과제 피드백
이번 주차에는 셀프 체크리스트가 없었습니다 ! (제가 못찾는 걸까요..)
셀프 체크리스트는 과제 체크리스트와 다르게 명확히 알아야 하는 것들이 적혀있어서 너무 좋았습니다 : )
체크리스트에 답변들을 정리하면서 마무리할 수 있는 시간이 되는 것 같아서 너무 좋았습니다.
리뷰 받고 싶은 내용
inputRef.current?.focus()처럼 옵셔널 체이닝을 사용하는 방식에 대한 코치님의 의견이 궁금합니다. 실무에서는 어떤 방식으로 타입 안정성을 확보하시나요?개발자 이력서에 성과를 "수치화"하는 것이 중요하다고 들었는데, 이미 퇴사하여 정확한 데이터에 접근할 수 없는 경우 어떻게 경험을 효과적으로 표현할 수 있을까요?
여담
코치님, 4월 10일자로 2025년이 된 지 벌써 100일이 되었네요.
코치님은 100일동안 어떤 시간을 보내셨나요?
저는 요즘 관심있는 MCP에 대해 블로그 글("해커뉴스를 Obsidian에 정리해주는 MCP 적용기")을 써보기도 했습니다.
타팀 코칭 시간에 "누군가에게 설명하는 과정에서 가장 많은 것들을 배운게 된다" 라는 부분이 조금이나마 이해하게 된 것 같습니다.
무작정 쓰던 MCP를 블로그에 쓰려고하니,
개념도 공부해야 되더라고요..매우 미흡한 글이지만.. 많은 도움이 된 것 같아서 기쁩니다 :)항해를 진행하면서 많은 것을 배우고, 그에 따른 자신감이 생겨서 올해 시작이 참 좋은 것 같습니다.
항상 많은 고민을 하고 만들어 주신 과제라는 생각이 드네요.
좋은 과제를 만들어 주셔서 감사드립니다 : )