[7팀 정건휘] Chapter 1-3. React, Beyond the Basics#44
[7팀 정건휘] Chapter 1-3. React, Beyond the Basics#44geonhwiii wants to merge 20 commits intohanghae-plus:mainfrom
Conversation
⭐ 팀 코드 리뷰용자랑하고 싶은 코드// useRef.ts
import { useState } from "react";
interface MutableRefObject<T> {
current: T;
}
/**
* 1. DOM 요소 null 초기화 대응
* const ref = useRef<HTMLDivElement>(null)
*
* 2. 초기값이 없는 경우 대응
* const ref = useRef<string>();
*/
export function useRef<T>(initialValue: T): MutableRefObject<T>;
export function useRef<T>(initialValue: T | null): MutableRefObject<T | null>;
export function useRef<T = undefined>(): MutableRefObject<T | undefined>;
export function useRef<T>(initialValue?: T): MutableRefObject<T | undefined> {
const [ref] = useState(() => ({ current: initialValue }));
return ref;
}실제 추가적인 타입 선언이 없을 경우,
// deepEquals.ts
export const deepEquals = (a: unknown, b: unknown) => {
if (a === b) return true;
if (a == null || b == null) return false;
if (typeof a !== typeof b) return false;
if (Array.isArray(a) && Array.isArray(b)) {
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;
}
if (typeof a === "object" && typeof b === "object") {
const objA = a as Record<string, unknown>;
const objB = b as Record<string, unknown>;
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false;
// 각 키의 값을 재귀적으로 비교
for (const key of keysA) {
if (!deepEquals(objA[key], objB[key])) return false;
}
return true;
}
return false;
};각 조건문이 의미하는 바가 명확하고, 이해가 간단하다고 생각해요. 개선이 필요하다고 생각하는 코드// AS-IS
export function useShallowState<T>(initialValue: T | (() => T)): [T, React.Dispatch<React.SetStateAction<T>>] {
const [state, setState] = useState(initialValue);
const setShallowState = useCallback((newValue: T | ((prev: T) => T)) => {
setState((prevState) => {
const nextState = typeof newValue === "function" ? (newValue as (prev: T) => T)(prevState) : newValue;
// 1. 얕은 비교 값이 같다면 이전 상태 반환
if (shallowEquals(nextState, prevState)) {
return prevState;
}
// 2. 값이 다르다면 새로운 상태 반환
return nextState;
});
}, []);
return [state, setShallowState];
}
// TO-BE
export function useShallowState<T>(initialValue: T | (() => T)): [T, React.Dispatch<React.SetStateAction<T>>] {
const [state, setState] = useState(initialValue);
const setShallowState = useCallback((newValue: T | ((prev: T) => T)) => {
setState((prevState) => {
const nextState = isFunction(newValue) ? newValue(prevState) : newValue;
// 1. 얕은 비교로 값이 같다면 이전 상태 반환
if (shallowEquals(nextState, prevState)) {
return prevState;
}
// 2. 값이 다르다면 새로운 상태 반환
return nextState;
});
}, []);
return [state, setShallowState];
}
function isFunction<T>(value: T | ((prev: T) => T)): value is (prev: T) => T {
return typeof value === "function";
}강제 타입캐스팅을 강제하는 부분을 줄이고 싶었어요. 타입 가드 함수를 활용해서, 강제 타입 캐스팅을 줄이고, 타입 안정성을 보완할 수 있을 것 같아요. |
|
메모이제이션에 대한 생각을 정리해주신 부분이 인상적입니다! |
Context API는 리액트 컴포넌트 트리 안에서 데이터를 공유할 수 있는 수단으로, 구조적으로 "상위 → 하위" 방향의 데이터 흐름에 적합해요. 하지만 값이 자주 바뀌는 상태를 Context로 전달하게 되면, 해당 Context를 구독 중인 모든 하위 컴포넌트가 리렌더링되는 문제가 생길 수 있어요. 이런 점에서 성능 측면의 단점이 존재하죠...! 반면, 따라서 약간 TMI인데,,, 이건 제가 Context를 사용하는 또 다른 하나의 방법입니다! 제가 다른 회사의 사전 과제 테스트를 수행할 때, 데이터 페칭이 많지 않거나 캐싱 등 복잡한 옵션이 필요하지 않은 경우에는 외부 라이브러리 도움 없이 react에서 제공하는 api들을 최대한 이용해서 다음과 같이 처리해요! <ErrorBoundary> -> Error 상태 처리
<Suspense> -> Loading 상태 처리
<CategoryFetcher> -> 서버에 저장된 카테고리 정보를 가져와서 저장하는 Context
<CategoryList /> -> Context에 저장된 카테고리 목록을 렌더링하는 UI
</CategoryFetcher>
</Suspense>
</ErrorBoundary> |
과제 체크포인트
배포 링크
https://geonhwiii.github.io/front_6th_chapter1-3/
기본과제
equalities
hooks
High Order Components
심화 과제
hooks
context
과제 셀프회고
React가 왜 이런 API를 제공하는지 이해할 수 있었습니다. 복잡한 내부 로직을 간단한 API로 감싸주는 추상화로 인해 사용자가 얼마나 편하게 사용할 수 있는지 배웠습니다.
또, React에서 제공하는
useState를 제외한 api는custom hook의 일종이라는 것도 깨달았습니다.기술적 성장
useRef가useState의lazy initialization을 활용한다는 원리를 이해useCallback과useMemo의 관계자랑하고 싶은 코드
실제
useRef사용과 비슷하게 사용할 수 있도록 타입 추론을 보완했어요.추가적인 타입 선언이 없을 경우,
const ref = useRef<HTMLDivElement>(null)는 타입 오류가 발생해요.각 조건문이 의미하는 바가 명확하고, 이해가 간단하다고 생각해요.
개선이 필요하다고 생각하는 코드
강제 타입캐스팅을 강제하는 부분을 줄이고 싶었어요.
타입 가드 함수를 활용해서, 강제 타입 캐스팅을 줄이고, 타입 안정성을 보완할 수 있을 것 같아요.
학습 효과 분석
이전의 사고방식:
현재의 사고방식:
과제 피드백
학습 갈무리
리액트의 렌더링이 어떻게 이루어지는지 정리해주세요.
리렌더링이 발생하는 조건:
렌더링 최적화
메모이제이션에 대한 나의 생각을 적어주세요.
메모이제이션은 비용이 많이 드는 곳에 사용하면 이점을 갖지만,
남용을 하게 될 경우, 코드의 복잡성이 증대될 수 있어요.
최근 리액트 컴파일러 발표 영상을 보면, 리액트 개발팀조차도 메모이제이션을 실수할 때도 있다고 언급한 부분을 보았을 때, 쉬운 영역이라고도 생각이 들지 않아요.
의도를 명확히 사용하고, 유지보수성을 고려해서 사용하면 좋을 것 같아요.
컨텍스트와 상태관리에 대한 나의 생각을 적어주세요.
FSD 아키텍처가 유행하는 이유는 대규모 아키텍처에 대한 고민도 있지만,
도메인, 기능에 대해서 관리 포인트를 가장 가까운 곳에서 관리하려는 것도 있다고 생각해요.
이와 마찬가지로, 상태도 가장 가까운 곳에서 관리하는 것이 중요해요.
컨텍스트는 Provider를 통해 의존성을 깊게 주입해야 하는 상황에서 사용하면 좋을 것 같아요.
useSyncExternalStore의 등장으로 리렌더링을 줄이면서, selector를 통해 선택적으로 상태에 대해 구독을 할 수 있게 되었어요.따라서, 개발자는 항상 비용을 고려해서 사용할 수 있도록 노력해야해요.
리뷰 받고 싶은 내용
useSyncExternalStore에 대한 이해가 아직은 부족한 것 같습니다.이번 과제에서 사용한
useStore와 같은store외에 활용 방법이 있을까요?그리고,
useSyncExternalStore를 단일로 사용하는 것보다context api와 결합해서 사용하는 패턴이 더 좋은 것일지,상황에 따라 선택하는 것이 맞을지 궁금합니다.