[1팀 주산들] Chapter 1-3. React, Beyond the Basics#33
[1팀 주산들] Chapter 1-3. React, Beyond the Basics#33DEV4N4 wants to merge 17 commits intohanghae-plus:mainfrom
Conversation
| if (Array.isArray(objA) && Array.isArray(objB)) { | ||
| if (objA.length !== objB.length) return false; | ||
|
|
||
| for (let i = 0; i < objA.length; i++) { | ||
| if (!Object.is(objA[i], objB[i])) return false; | ||
| } | ||
|
|
||
| return true; | ||
| } |
There was a problem hiding this comment.
사실 배열도 타입이 'object'여서 이부분이 없어도 통과될거에요 :)
배열을 Object.keys() 메서드로 접근시 인덱스가 key가 됩니다!
| /* eslint-disable @typescript-eslint/no-unused-vars */ | ||
| import type { DependencyList } from "react"; | ||
| import { shallowEquals } from "../equals"; | ||
| import { useRef } from "./useRef"; | ||
|
|
||
| export function useMemo<T>(factory: () => T, _deps: DependencyList, _equals = shallowEquals): T { | ||
| // 직접 작성한 useRef를 통해서 만들어보세요. | ||
| return factory(); | ||
|
|
||
| // 1. 이전 의존성과 결과를 저장할 ref 생성 | ||
| const ref = useRef<{ | ||
| deps?: DependencyList; | ||
| result?: T; | ||
| }>({}); | ||
|
|
||
| // 2. 현재 의존성과 이전 의존성 비교 | ||
| // 3. 의존성이 변경된 경우 factory 함수 실행 및 결과 저장 | ||
| if (!ref.current.deps || !_equals(ref.current.deps, _deps)) { | ||
| ref.current.deps = _deps; | ||
| ref.current.result = factory(); | ||
| } | ||
|
|
||
| // 4. 메모이제이션된 값 반환 | ||
| return ref.current.result as T; | ||
| } |
There was a problem hiding this comment.
ref.current.result가 옵셔널이기 때문에 undefined가 될 수 있어 as T 단언을 해주어야 하는것으로 보여요!
ref 값에 빈 객체 {} 대신 초기값
const ref = useRef<{
deps?: DependencyList;
result: T;
}>({
deps: _deps,
result: factory(),
});을 설정해주면 타입스크립트가 T로 추론할 것 같습니당
jinsoul75
left a comment
There was a problem hiding this comment.
산들님 PR은 옆에서 말로 설명해주는것 같이 술술 읽히네요 ㅎㅎㅎ 3주차 과제도 고생많으셨습니당 :) ~
| const objKey = objAKeys[i]; | ||
|
|
||
| if (!Object.hasOwn(objB, objKey)) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
이런 식으로 b 객체에 a 객체의 키가 존재하는지 확인하여 먼저 리턴하는 방법도 있군요!!
objKey 대신 objAkey 처럼 a 객체의 키값이라는 걸 명시해주는건 어떨까요!? 🤩
| return Component; | ||
| return function MemoizedComponent(props: P) { | ||
| const prevPropsRef = useRef<P | null>(null); | ||
| const renderedResultRef = useRef<ReactNode | Promise<ReactNode> | null>(null); |
There was a problem hiding this comment.
useRef 안에 ReactNode 와 Promise 사용한 이유가 궁금해요 ! (저는 ReactNode 를 안써봐서요!!)
There was a problem hiding this comment.
저는 <ReturnType<typeof Component> | null>으로 renderedResultRef 타입 해줬어요!
| } | ||
| }, []); | ||
|
|
||
| return [state, setStateWithShallowEquals] as const; |
There was a problem hiding this comment.
오 useState 형식으로 return한게 인상이 깊네요!! 별칭(const) 없이도 쓸수 있는지가 궁금해요. 그리고 왜 const로 지정했는지도 궁금해요~
과제 체크포인트
배포 링크
https://dev4n4.github.io/front_6th_chapter1-3/
기본과제
equalities
hooks
High Order Components
심화 과제
hooks
context
과제 셀프회고
React의 Hook들에 대해 deepdive 해보는 계기가 되어서 좋았다.
Hook들은 당연히 JS로 구현이 되어 있었겠지만… Hook을 직접! JS로 작성해 볼 수도 있다는 생각은 안해봤는데 이렇게 과제로 제시받아 직접 해보니까 이해도가 올라가고 Hook의 내부 구조에 대해 고민해보고 알 수 있게 되어서 정말 좋았다..
나는 기초가 부족한 편이었다고 스스로 생각하고 있었기도 해서 이번 기회에 보완하는 데 많은 도움을 받았던 것 같다.
기술적 성장
👍 새로 학습한 개념
Object.is
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is
이번 과제에서 많이 사용했던 메서드이고 == 연산자, === 연산자와 어떤 점이 다른지에 대해 잘 아는 계기가 되었던 것 같다.
==연산자는 같음을 테스트하기 전에 양 쪽(이 같은 형이 아니라면)에 다양한 강제(coercion)를 적용하지만("" == false가true가 되는 것과 같은 행동을 초래),Object.is는 어느 값도 강제하지 않습니다.Object.is()는 === 연산자와도 같지 않습니다.Object.is()와===의 유일한 차이는 부호 있는 0과 NaN 값들의 처리입니다.===연산자(및==연산자)는 숫자값-0과+0을 같게 처리하지만, NaN은 서로 같지 않게 처리합니다.Object.prototype.hasOwnProperty() 대신 Object.hasOwn() 사용하기
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn
지금까지 Object.prototype.hasOwnProperty()를 사용해 왔었는데, hasOwn을 사용하는게 권장된다는 걸 이번에 첨 알았다!
💪 기존 지식의 재발견/심화
useRef
useState를 활용해 useRef를 만들 수 있다는 것을 이번에 알았다!
두 Hook에 대해 다 알고는 있었는데… 이렇게 구현될 수 있다는 것이 신기했고
생각해보면 그렇지 싶은데 왜 지금까지는 별 생각이 없었을까… 사실 잘 몰랐던 게 아닐까 싶었다.
const [value] = useState(...)로 Setter 함수를 무시하면, 해당 객체를 변경해도 리렌더링이 발생하지 않는다.useState를 사용하면 상태를 업데이트할 때setState를 호출해 리렌더링을 유발하지만, 여기서는 setter를 전혀 사용하지 않기 때문에.current만 변경해도 화면 갱신이 일어나지 않는다.즉,
value.current = 새로운값처럼 직접 할당해도 React는 이 변경을 인지하지 않으므로 리렌더링을 하지 않는다.위 방식은 공식
useRef훅의 동작 원리와 같다.useRef는 한 번만 생성된 객체를 기억해 두었다가, 렌더링마다 동일한 객체를 반환한다. 그 객체의.current프로퍼티를 바꿔도 React가 다시 렌더링하지 않는다.useState의 “초기 상태 유지” 특성을 활용해 저장소(ref)를 만든 것이다.useMemo
useMemo가 이렇게 의존성들을 직접 비교 하면서 메모이제이션을 해주는 것이 신기했다. (생각해보면 비교가 당연히 들어갈텐데 그동안은 왜 이렇게 마법같이 막연하게 느껴졌었는지..?)
자랑하고 싶은 코드
대체로 정답이 있는 코드들 같아서 자랑하고 싶은 코드가 크게 생각나지는 않는다.
그래도 굳이 자랑할 코드를 생각해보자면 equal 함수들을 AI를 사용하지 않고 직접 생각하면서 구현했다는거..? 리팩토링도 한번 했다는거!?
개선이 필요하다고 생각하는 코드
으음… 이것도 대체로 정답이 있는 코드를 정답의 원인을 찾아가면서 적어나갔던 거라 크게 생각이 나지 않는다.
그래도 전반적으로 자잘하게 (변수명이라던가) 리팩토링을 해서 가독성을 좋게 만들면 더 좋지 않을까 하는 생각이 든다.
학습 효과 분석
React의 Hook들에 대해 deepdive 해 볼 수 있었던 시간이었던 것 같아서 좋았다.
공식 문서를 정독하고, Hook들을 해부해 보면서 막연하게만 생각했던 구조가 현실적으로 다가와 이해도가 깊어졌던 것 같다.
이전에는 뭐랄까 JS와 React를 따로 생각했었는데 지금은 React가 JS 코드로 어떻게 만들어졌는지를 보니까
기초가 탄탄해서 라이브러리에 구애받지 않고 개발을 잘 하는 것에 대해서도 생각을 해보게 되는 것 같고…
기초가 중요하다, JS에 대해서 깊게 이해하는 것이 중요하다 라고 말로만 듣고 실제로 체감해본 적은 없는데
이번 과제들을 수행하며 왜 기초를 탄탄히 해야하는지 직접 체감하고(다 JS로 이루어져있구나 하고 실감이 가서), 앞으로 어떻게 공부를 해야 할 지에 대해서도 감을 잡을 수 있었던 것 같다.
나중에 React나 기타 라이브러리들의 repo를 뜯어보고 싶다는 생각도 들었다!
과제 피드백
기초 과제가 너무 좋았어요! Hook에 대해서 깊은 이해를 할 수 있어서 정말 유익하고 진행하면서 배운 것도 많았어요. 이전 주차들과 연계되는 느낌이 들어서 이해도 빨랐어요!!
심화 과제는 조금 어려웠어요.. AI의 도움을 받으면서 진행을 했고 잘 해결되긴 했으나 아직도 약간 헷갈리는 것 같아요.
Toast에서 좀 헤맸어요! “Context를 분리해야겠다!” 까지는 생각이 금방 도달했는데 useCallback을 쓸 지, useAutoCallback을 쓸 지, useMemo를 쓸 지 고민하고 이것저것 삽질을 했던 것 같아요.
근데 삽질하면서 이전에 구현했던 부분들을 다시 읽어보면서 복습도 되고 좋았어요!
학습 갈무리
리액트의 렌더링이 어떻게 이루어지는지 정리해주세요.
리액트의 랜더링 과정
리액트의 렌더링 최적화 방법
React.memo,useMemo,useCallback을 활용해 불필요한 리렌더링을 방지할 수 있습니다.key값을 적절히 부여하거나shouldComponentUpdate,PureComponent를 사용하는 것도 최적화 방법 중 하나입니다.리액트의 렌더링과 관련된 개념
렌더링과 관련된 라이프사이클 & Hook
componentDidMount,shouldComponentUpdate,componentDidUpdate등useEffect,useMemo,useCallback,useLayoutEffect,useSyncExternalStore등메모이제이션에 대한 나의 생각을 적어주세요.
메모이제이션
useMemo,useCallback,React.memo등으로 값이나 함수의 재생성을 방지할 수 있습니다.메모이제이션이 언제 필요할까?
메모이제이션을 사용하지 않으면?
장점
단점
메모이제이션을 사용하지 않고도 해결할 수 있는 방법
컨텍스트와 상태관리에 대한 나의 생각을 적어주세요.
컨텍스트와 상태관리가 필요한 이유
컨텍스트와 상태관리를 사용하지 않으면?
장점
단점
컨텍스트와 상태관리를 사용하지 않고도 해결할 수 있는 방법
사용할 때 주의할 점
리뷰 받고 싶은 내용
useMemo, useCallback, useAutoCallback을 구현할 때 마지막으로 값을 리턴할 때 타입 단언을 사용하여 “as T” 형식으로 리턴하였는데 이보다 나은 방법이 있을 지 궁금합니다. 약간 “any” 처럼 TS의 검사를 소홀히 하는 방식으로 빠져나간게 아닌가 싶어서요. 근데 보기에는 깔끔해 보이니 괜찮은가 싶기도 한데 TS의 의도대로 사용하지 못한 것 같기도 해서 자주 쓰면 안좋은 문법인가 하는 생각이 들기도 합니다. 코치님께서는 타입 단언을 사용하여 구현하는 것에 대해 어떻게 생각하시나요? 해당 케이스들에서는 어떻게 구현하는 것을 추천하시나요?
ToastProvider 구현에 관한 질문입니다. 지금은 Context를 나누고, useMemo를 사용해서 최적화를 진행하였습니다. 그런데 이게 최선일지… 의도하신 대로 제가 문제를 잘 푼건지 궁금합니다. 뭔가 비슷한 형태의 코드가 반복되는 것도 같은데 반복되는 부분이 Context 선언하는 부분이니까 어쩔수 없나 싶기도 하면서도 여기서 조금 더 코드를 개선할 수도 있지 않을까 생각이 들기도 하네요. 여기서 조금 더 깔끔하고 예쁘게 코드를 고치려면 어떻게 나아가야 할까요?