[4팀 이유진] Chapter 1-3. React, Beyond the Basics#32
Conversation
|
우리 유진 님 파이팅 💪 |
|
안녕하세요 공주님! 접니다! 퇴근 시간이 24분 가량 남아서.. 유진 공주님의 PR 보러 왔습니다 👀 |
There was a problem hiding this comment.
🐳 자랑하고 싶은 코드
모두 비슷하게 작성하셨을 useMemo일 테지만..!ㅋㅋㅋ
그래도 스스로 오래 고민하고 구현해서 마음에 듬니다아...
There was a problem hiding this comment.
잘했습니다 공주님!!!
사실 이번 과제는 다 쪼끔씩 다르지 뭔가 구현 자체는 다 비슷비슷하더라구요 ㅋㅋㅋㅋㅋ
There was a problem hiding this comment.
와아아👏👏👏
저는 다른거지만 저도 자랑하고 싶은 코드는 제가 찾아서 했다는데 의의둔 코드였습니다.
| for (let i = 0; i < objA.length; i++) { | ||
| if (!deepEquals(objA[i], objB[i])) return false; | ||
| } | ||
| return true; |
There was a problem hiding this comment.
🦐 고민되는 부분
팀원분들께서는 배열을 따로 처리해주셨는지 궁금합니다!
There was a problem hiding this comment.
// 배열인지 확인
const isArrayA = Array.isArray(a);
const isArrayB = Array.isArray(b);
if (isArrayA !== isArrayB) return false;
if (isArrayA && isArrayB) {
// 길이 다르면 false
if (a.length !== b.length) return false;
// 배열의 각 요소를 재귀적으로 비교
// 요소가 배열, 객체일 수도 있으니까
for (let i = 0; i < a.length; i++) {
if (!deepEquals(a[i], b[i])) return false;
}
return true;
}네! 배열 처리 했습니다!
There was a problem hiding this comment.
저는 객체 비교하는 코드로도 커버 가능하다고 생각해서 하지 않았습니다ㅎㅎ
금요일 준일 코치님 공개 코드 리뷰 세션 금주 멘토링에서 배열을 따로 처리하면 더 간단하다?라고 말씀하셨던 것 같은데,
성능상 얼마나 차이가 날지도 궁금하네용!
export const deepEquals = (a: unknown, b: unknown) => {
if (a === b) return true;
if (!isObject(a) || !isObject(b)) return false;
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
if (aKeys.length !== bKeys.length) return false;
for (const key of aKeys) {
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
if (!deepEquals(a[key], b[key])) return false;
}
return true;
};There was a problem hiding this comment.
저도 하늘님과 비슷하게 적었습니다.변수만 다르고 동일하게 한...;;;
| // undefined는 첫 렌더 | ||
| // resultRef는 factory()의 실행결과 저장 | ||
| const depsRef = useRef<DependencyList | undefined>(undefined); | ||
| const resultRef = useRef<T | undefined>(undefined); |
There was a problem hiding this comment.
코치님께 리뷰 받고 싶은 내용에 하나의 객체로 두는 게 나을지, 아니면 각각 분리할지(지금 유진님처럼).. 이 있는데
저는 일단 합쳐서 넣었어요.
const cache = useRef({ deps: null, result: null });나는 왜 이렇게 했는가? → 그냥 제 눈에 예뻐서입니다.
useRef를 두 번 호출하기보다는, 한 번에 호출하고,
같은 관심사 코드를 같이 묶어 두는 게 제 취향이에요. (변태 같죠? ㅋ)
코치님의 의견은 다르실 수도 있겠지만.. useRef를 두 번 호출한다고 해서 성능 차이가 있는 것도 아니고, 미미하대요.
곧 있을 클린 코드 & 디자인 패턴 & 관심사 분리 챕터에서 이런 내용이 나오지 않을까 기대되네요오. 🤔
| // useSyncExternalStore 에서 활용할 수 있도록 subscribe 함수를 수정합니다. | ||
| const subscribe = (fn: Listener) => { | ||
| listeners.add(fn); | ||
| return () => unsubscribe(fn); |
There was a problem hiding this comment.
const subscribe = (callback) => {
// 구독 로직
return () => {
// 구독 해제 로직
};
};저는 return 함수에 바로 listeners.delete(fn);을 넣었습니다!
이유:
- unsubscribe 함수를 다른 데에서 쓸 일이 없을 것 같아서.
- useSyncExternalStore 훅이 그런 방식을 취하고 있어서.
- 직접 listeners.delete(fn)를 쓰면 함수 호출 오버헤드가 하나 줄어듦.
// useSyncExternalStore
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}유진 님께서는 unsubscribe 함수를 어딘가에서 쓰일 수 있다 생각하여 return에 호출하신 건가요?!
There was a problem hiding this comment.
저도 유진님처럼 unsubscribe를 지우지 않고 사용했는데,
보는 사람 입장에서 unsubscribe를 return하면
해당 함수가 구독 해제 함수를 return하는구나 라고 직관적으로 알 수 있을 것 같아서 그렇게 했어용!
과제 체크포인트
배포 링크
https://elli-lee.github.io/front_6th_chapter1-3/
기본과제
equalities
hooks
High Order Components
심화 과제
hooks
context
과제 셀프회고
이번 과제가 1주차 세번의 과제 중 정답과 방향이 가장 명확해서 비교적 수월했기 때문에, 왜 이렇게 동작하도록 함수를 작성해야 하는지를 명확히 이해하고자 노력했고, 각 함수에서 처리해야할 로직들을 AI 도움을 최소화해서 구현하려고 노력했습니다 .리액트의 여러 훅들을 직접 구현하면서 훅들의 동작 원리를 알 수 있었고, 리액트가 무엇을 해결하고자 했는지가 조금씩 느껴졌습니다.
3주간 프레임워크 없이 SPA 만들기를 진행하면서 SPA 프레임워크의 동작 원리를 어느정도 알고있다고 생각했는데 알고있기는 커녕 저는 여태껏 궁금해 한 적 조차 없었다는 사실을 깨달았고, 자바스크립트 실력이 많이 부족하다는 것도 느꼈습니다.
저의 부족함을 많이 알게된 3주였고, 제 과제의 결과물이 제 스스로도 만족할 만큼의 수준은 아니지만(특히 1주차 과제..시간되는대로 꼭 다시 도전해보고 싶어요..), 3주간의 몰입이 돌아보니 정말 재밌었고, 개인적으로는 많이 성장했다고 생각합니다!
기술적 성장
** Equalities 구현 과정에서 **
어떤 타입을 먼저 처리해야 하는지, 각 분기 처리를 거칠 때마다 어떤 타입으로 좁혀지는지, 잘못 처리된 타입은 없는지 신경쓰며 구현했습니다.
이 과정에서 typeof null은 object라는 사실을 처음 알게 되어 object 타입을 처리하기 전 null을 먼저 처리해주었습니다.
** useRef 구현 과정에서 **
어떻게 내부적으로 useState를 사용하는데 리렌더링을 발생시키지 않을 수 있을지 이해하는데 시간이 걸렸습니다.
useState는 초기화 시에만 객체를 생성하고 이후 리렌더링에서는 동일한 객체 참조를 반환하고, useState의 setter를 호출하지 않는 한 리렌더링이 발생하지 않는다.
React는 객체 내부 프로퍼티 변화(current의 변화)를 감지하지 못한다 (얕은 비교!)
는 점을 알게 되었습니다.
useState의 구조분해 할당으로 state만 받고 setter 함수는 아예 안받는 이유가 궁금했는데, setter가 리렌더링을 유발하기 때문에 useRef에서는 필요없어서 안 받았다는 아주아주 당연한 사실도 새삼 알게 되었습니다..
또한, 테스트 코드를 통해 useRef가 수행해야하는 결과를 이해하고자 노력했는데, 중복을 걸러주는 Set 자료구조를 사용해서 Set의 size를 통해 리렌더링 시 참조가 변했는지를 체크하는 점이 인상깊었습니다.
** useMemo 구현 과정에서 **
useMemo를 구현하면서 궁금했던 부분은 왜 deps를 깊은비교가 아닌 얕은 비교로 수행하는지 였습니다.
찾아본 결과,
깊은 비교는 비용이 너무 크다!
만약 deps를 깊은 비교(deep equality)로 검사하려면:
배열의 각 요소가 객체일 경우 그 안의 속성까지 전부 비교해야 하는데, 이건 성능 비용이 크고, 특히 렌더링마다 비교하게 되면 전체 앱의 성능이 떨어질 수 있기 때문임을 알게 되었습니다.
** useCallback 구현 과정에서 **
리액트를 제대로 사용해본 적이 없는 저는... React.memo로도 충분할것 같은데 왜 useCallback이 필요한지 궁금했습니다.
핵심은 함수도 결국 객체이기 때문에 그 함수를 가지고 있는 부모컴포넌트가 리렌더 될 때마다 다시 생성된 새 함수가 되어 참조값이 달라지기 때문이었습니다.
자식 컴포넌트에 memo가 적용되어 있어도, 부모 컴포넌트가 자식 컴포넌트에게 함수를 전달하고 있는 경우, 부모컴포넌트가 리렌더링 될 때마다 함수의 참조값이 바뀌므로 자식컴포넌트는 props가 바뀌었다고 판단하기 때문에 memo와 관계없이 리렌더링되기 때문임을 알게 되었습니다.
자랑하고 싶은 코드
자랑할 만한 코드를 찾는것... 정말 어려운 일입니다..🥹
그나마 찾아보자면..
코드적으로 자랑하고 싶다기 보다는..
useMemo의 동작을 이해하기 위해 오래 고민하고 공부하고 스스로 구현했다는 점에서 당첨되었습니다.
개선이 필요하다고 생각하는 코드
deepEquals에서
객체 처리할 때 배열을 먼저 별도로 처리했는데요,
구현할 때는 배열을 별도로 분기처리 안했더니 테스트코드를 통과하지 못해서 분기처리를 했었는데,,
과연 정말 필요한 분기 처리였을까, 분기처리의 문제가 아니라 기존 로직 자체에 문제가 있었을 수도 있겠다하는 생각이 듭니다.
학습 효과 분석
** 가장 큰 배움이 있었던 부분 **
React의 렌더링 최적화 메커니즘을 이해하게 되었습니다. 특히 의존성 배열의 비교 방식과 메모이제이션의 실제 동작 원리를 알 수 있었습니다.
** 추가 학습이 필요한 영역 **
복잡한 타입스크립트 이슈가 발생하면 타입 단언으로 처리하거나 타입 오류를 해결해달라고 AI에게 요청..해서 해결했는데, 이에 대한 추가적인 학습이 필요할 것 같습니다.
개인적으로는 과제를 다 진행하고 나니, 실무에서 사용하고 있는 Vue의 동작 원리와 내부 구현도 궁금해졌습니다.
과제 피드백
앞에서 구현한 함수를 그 다음 함수를 구현하는데 사용하도록 설계되어 왜 이렇게 동작해야 하는지를 명확히 이해할 수 있어서 좋았습니다. 또한 실제 React 내부 구현과 유사한 방식으로 설계되어 리액트 deep dive 경험을 할 수 있어서 좋았습니다.
학습 갈무리
리액트의 렌더링이 어떻게 이루어지는지 정리해주세요.
리액트의 렌더링 과정:
리액트는 상태(state)나 props가 변경되었을 때 컴포넌트를 다시 렌더링합니다.
리액트의 렌더링 최적화 방법:
메모이제이션에 대한 나의 생각을 적어주세요.
메모이제이션이 필요한 경우:
장점:
단점:
제가 생각하는 사용법:
useMemo는 언제 사용하면 좋을지 조금 감이 오는 것 같은데, React.memo는 언제 적용해야 할지 판단이 어렵습니다...
컨텍스트와 상태관리에 대한 나의 생각을 적어주세요.
컨텍스트와 상태관리가 필요한 이유:
컨텍스트와 상태관리를 사용하지 않으면 발생하는 문제:
사용했을 때의 장점:
사용했을 때의 단점:
사용 시 주의할 점:
리뷰 받고 싶은 내용
저는
이렇게 별도로 두긴 했는데요, (이유는.. 객체로 다루는 것이 비교 등등에서 신경 쓸 포인트가 늘어날 수도 있겠다...는 생각이었습니다)
이렇게 하나의 객체로 두는 것이 더 나은 구조인지, 코치님께서는 어떤 방식을 선호하시는지 궁금합니다.
과제 구현 과정에서 타입 이슈 해결을 위해서 타입 단언을 사용한 부분이 꽤 있습니다.
이 중에서 특히 useMemo의 return 에서 한 타입 단언이 안전한지 궁금합니다.