Skip to content

Conversation

@E-JIWON
Copy link

@E-JIWON E-JIWON commented Apr 9, 2025

배포 링크

https://e-jiwon.github.io/front_5th_chapter1-3/

과제 체크포인트

기본과제

  • shallowEquals 구현 완료
  • deepEquals 구현 완료
  • memo 구현 완료
  • deepMemo 구현 완료
  • useRef 구현 완료
  • useMemo 구현 완료
  • useDeepMemo 구현 완료
  • useCallback 구현 완료

심화 과제

  • 기본과제에서 작성한 hook을 이용하여 렌더링 최적화를 진행하였다.
  • Context 코드를 개선하여 렌더링을 최소화하였다.

과제 셀프회고 : )

항상 유익했지만 이번에는 102381배는 유익한 느낌이 들었습니다.
당장 실무에 적용해보고 싶은 느낌..! (백수라서 적용할 실무가 없는게 안타깝습니다.)

기술적 성장

참조 동등성

React의 성능 최적화를 이해하기 위해서는 먼저 '참조 동등성(Referential Equality)'이라는 개념을 확실히 이해해야 하는 것을 알았습니다.
제가 Hooks를 구현하면서 가장 중요하게 느꼈던 부분입니다.

  • 참조 동등성은 두 변수가 메모리상의 동일한 객체를 가리키고 있는지를 확인하는 개념입니다.
  • JavaScript에서는 === 연산자나 Object.is()를 통해 참조 동등성을 확인할 수 있었습니다.
// 기본 타입 비교
const a = 5;
const b = 5;
console.log(a === b); // true (값이 같음)

// 객체 비교
const obj1 = { name: "Kim" };
const obj2 = { name: "Kim" };
const obj3 = obj1;

console.log(obj1 === obj2); // false (다른 객체 참조)
console.log(obj1 === obj3); // true (같은 객체 참조)

이 개념이 중요한 이유는 React가 컴포넌트 리렌더링을 결정할 때 props와 state의 참조 동등성을 확인하기 때문
즉, 객체나 배열의 내용이 동일하더라도 참조가 다르면 React는 이를 변경된 것으로 간주하고 리렌더링을 발생시키는 것을 학습했습니다.


useRef 구현으로 배우는 불변성 관리

값이 변경되어도 리렌더링을 발생시키지 않는 특별한 Hook

function useRef<T>(initialValue: T) {
  const [ref] = useState<{ current: T }>(() => {
    return { current: initialValue };
  });
  
  return ref;
}

ref.current = '새 값'과 같이 값을 업데이트하면, 객체의 속성만 변경될 뿐 객체 자체의 참조는 변경되지 않습니다.
따라서 React는 이 변화를 감지하지 못하고 리렌더링을 발생시키지 않습니다.

여기서 얻은 중요한 인사이트

  1. useState는 참조 변경을 감지한다. - 새로운 메모리 주소를 가진 값을 설정하면 리렌더링이 발생한다.
  2. 객체 속성 변경은 참조 변경이 아니다 - 같은 객체의 속성을 변경하는 것은 참조 변경이 아니므로 리렌더링을 발생시키지 않는다.

얕은 비교와 깊은 비교: useMemo와 useCallback의 핵심

useMemo와 useCallback을 구현하면서 가장 중요했던 부분은 의존성 배열의 비교 방식이라고 생각합니다.
기본적으로 React는 얕은 비교를 합니다.

얕은 비교 코드 구현
export function shallowEquals<T>(objA: T, objB: T): boolean {

// 기본 타입 비교 (number, string, boolean, null, undefined)
if (Object.is(objA, objB)) return true;  

// null이나 undefined 체크
if (objA == null || objB == null) return false;  

// 타입이 다른 경우
if (typeof objA !== typeof objB) return false;  

// 배열 비교
if (Array.isArray(objA) && Array.isArray(objB)) {
	// 배열 길이 비교
	if (objA.length !== objB.length) return false;
	return objA.every((v, idx) => Object.is(v, objB[idx]));
}
  

// 객체 비교

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(objATyped);

	if (keysA.length !== keysB.length) return false;  

	return keysA.every(
		(key) =>
			Object.prototype.hasOwnProperty.call(objBTyped, key) && 
			Object.is(objATyped[key], objBTyped[key])
		);
	}
	return false;
}

얕은 비교는 객체의 최상위 속성들만 비교합니다.
객체나 배열 같은 중첩된 참조 타입은 참조 동등성만 확인합니다.

깊은 비교 코드 구현
export function deepEquals<T>(objA: T, objB: T): boolean {
	// 기본 타입 비교 (number, string, boolean, null, undefined)
	if (Object.is(objA, objB)) return true;

	// null이나 undefined 체크
	if (objA == null || objB == null) return false;
	
	// 타입이 다른 경우
	if (typeof objA !== typeof objB) return false;
	
	// 모든 요소를 철저하게 비교..
	if (Array.isArray(objA) && Array.isArray(objB)) {
		if (objA.length !== objB.length) return false;
		
		for (let i = 0; i < objA.length; i++) {
			if (!deepEquals(objA[i], objB[i])) return false;
		}
		return true;
	}
	// 객체 비교 (배열이 아닌 객체ㅋㅋ)
	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;
	}
	return false;
}
  • 깊은 비교는 모든 중첩된 객체와 비열 내용까지 재귀적으로 비교하여 깊은 비교를 합니다!
얕은 비교 vs 깊은 비교 언제 써야 할까?
  • 얕은 비교: 단순한 객체나 배열을 비교할 때, 성능이 중요할 때 사용
  • 깊은 비교: 중첩된 데이터 구조를 정확히 비교해야 할 때 사용해야 한다. 다만 객체가 크고 깊을수록 성능 비용이 증가합니다.
// 얕은 비교 사용 (기본)
const result = useMemo(() => expensiveCalculation(data), [data]);

// 깊은 비교 사용 (중첩된 객체의 내용 변화를 감지하고 싶을 때)
const result = useMemo(() => expensiveCalculation(data), [data], deepEquals);

useMemo 구현으로 배우는 메모이제이션

useMemo는 계산 비용이 많이 드는 값을
메모이제이션(캐싱)하여 불필요한 재계산을 방지합니다.

스크린샷 2025-04-11 오전 5 37 24 구현은 인자에서 힌트를 많이 얻었는데요 의존성 변경되었는지 확인한 후에 변경되었다면 factory 함수를 실행하여 새 값을 계산, 아닌 경우엔 이미 계산된 값을 재사용하도록 구현하였습니다.

useCallback 구현으로 배우는 함수 메모이제이션

함수 자체를 메모이제이션하기 위한 Hook.
구현 자체는 useMemo와 매우 유사합니다.

  export function useCallback<T extends Function>(
	callback: T,
	deps: DependencyList,
	equals = shallowEquals
) {
	// 이전 의존성과 콜백 함수를 저장하기 위한 ref 생성
	const ref = useRef<CallbackState<T>>({
		deps: [],
		callback: callback,
		initialized: false,
	});
	
	// 의존성 변경 여부 확인
	const depsChanged = !ref.current.initialized || !equals(deps, ref.current.deps);

	// 의존성 변경 or no 초기화 -> 콜백 업데이트
	if (depsChanged) {
		ref.current.deps = deps;
		ref.current.callback = callback;
		ref.current.initialized = true;
	}
	return ref.current.callback;
}
useCallback의 실제 활용

useCallback은 자식 컴포넌트에 전달되는 이벤트 핸들러를 메모이제이션할 때 특히 유용했습니다.
React.memo와 함께 사용할 때 그 진가를 발휘한다고 하여 내용을 정리해보았습니다.

// 부모 컴포넌트
function ParentComponent() {
  const [count, setCount] = useState(0);
  
  // 리렌더링마다 새로운 함수 참조가 생성됨
  const handleClickWithoutCallback = () => {
    console.log('Clicked!');
  };
  
  // 의존성 배열이 변경되지 않는 한 동일한 함수 참조 유지
  const handleClickWithCallback = useCallback(() => {
    console.log('Clicked!');
  }, []);
  
  return (
    <>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponentWithMemo onClick={handleClickWithCallback} />
    </>
  );
}

// React.memo로 최적화된 자식 컴포넌트
const ChildComponentWithMemo = React.memo(function ChildComponent({ onClick }) {
  console.log('Child rendered');
  return <button onClick={onClick}>Click me</button>;
});

useCallback을 사용하지 않으면, 부모 컴포넌트가 리렌더링될 때마다 새로운 함수가 생성되어 자식 컴포넌트로 전달됩니다.
이로 인해 React.memo의 효과가 사라지고, 자식 컴포넌트가 불필요하게 리렌더링되었습니다.


React.memo 구현으로 배우는 HOC(고차 컴포넌트)

React.memo는 컴포넌트를 메모이제이션하기 위한 HOC(Higher-Order Fuctions)입니다.
이부분은 읽고 또 읽어도 너무 어려웠는데요,, 구현하면서 이해하려고 노력했습니다!

export function memo<P extends object>(
  Component: ComponentType<P>,
  equals = shallowEquals
) {
  return function MemoizedComponent(props: P) {
    const propsRef = useRef<P | null>(null); // 이전 props 저장
    const componentRef = useRef<ReactElement | null>(null); // 마지막 렌더링 컴포넌트 저장

    // 렌더링 필요 여부 확인
    // 1. 첫 렌더링이거나 props가 변경된 경우
    // 2. 키 개수가 다른 경우
    // 3. 키 값이 다른 경우
    const shouldRender =
      propsRef.current === null ||
      !equals(propsRef.current, props) ||
      Object.keys(propsRef.current).length !== Object.keys(props).length;

    // 렌더링이 필요한 경우에만 새 컴포넌트 생성
    if (shouldRender) {
      propsRef.current = props; // 현재 props 저장
      componentRef.current = createElement(Component, props); // 새 컴포넌트 생성
    }

    return componentRef.current; // 현재 또는 이전 렌더링 결과 반환
  };
}

HOC 관점에서 설명하자면, memo는 ((컴포넌트))를 인자로 받아서 -> 최적화된 ((컴포넌트))를 생성하여 반환합니다.
-> 컴포넌트를 인자로 받고, 컴포넌트를 생성하여 반환한다고 하여 HOC로 이해했습니다!

최적화 로직 자체를 모듈화해서 재사용한다는 장점이 있고, 내부적으로 props를 비교 로직 처리하여 로직 자체는 숨긴다는 장점을 가지고 있는 것 같습니다!

React.memo의 최적화 전략
  1. 컴포넌트 분할(Component Split) - 상태 변경에 따라 리렌더링이 필요한 부분과 그렇지 않은 부분을 분리
  2. Props 설계 - 객체나 함수를 props로 전달할 때는 메모이제이션을 고려
  3. 상태 위치 - 상태를 사용하는 컴포넌트 근처에 배치하여 불필요한 리렌더링을 최소
// 좋지 않은 방식 - 모든 아이템이 리렌더링됨
function ItemList({ items, onItemClick }) {
  return (
    <div>
      {items.map(item => (
        <Item key={item.id} item={item} onClick={onItemClick} />
      ))}
    </div>
  );
}

// 개선된 방식 - 변경된 아이템만 리렌더링됨
function ItemList({ items, onItemClick }) {
  return (
    <div>
      {items.map(item => (
        <MemoizedItem key={item.id} item={item} onClick={onItemClick} />
      ))}
    </div>
  );
}

const MemoizedItem = React.memo(function Item({ item, onClick }) {
  return <div onClick={() => onClick(item.id)}>{item.name}</div>;
});

컴포넌트 최적화 전략: 렌더링 최소화하기

과제를 통해 얻은 가장 큰 인사이트는 컴포넌트 최적화에 관한 것이었습니다.

컴포넌트 분할 전략
// 좋지 않은 방식: 모든 것이 한 컴포넌트에 있음
function UserDashboard({ user }) {
  const [searchTerm, setSearchTerm] = useState('');
  
  return (
    <div>
      <UserProfile user={user} />
      <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
      <ExpensiveList items={items} filter={searchTerm} />
    </div>
  );
}

// 개선된 방식: 변경되는 부분 분리
function UserDashboard({ user }) {
  const [searchTerm, setSearchTerm] = useState('');
  
  return (
    <div>
      <MemoizedUserProfile user={user} />
      <SearchBox
        value={searchTerm}
        onChange={setSearchTerm}
      />
      <MemoizedExpensiveList 
        items={items} 
        filter={searchTerm} 
      />
    </div>
  );
}
2. 상태 설계 및 Props 인터페이스

이 부분은 타팀 멘토링 시간에 얻어간 인사이트입니다!

// 좋지 않은 방식: 큰 객체 하나를 props로 전달
function UserCard({ userData }) {
  return (
    <div>
      <h2>{userData.name}</h2>
      <p>{userData.email}</p>
      {/* userData 객체의 일부만 사용 */}
    </div>
  );
}

// 개선된 방식: 필요한 props만 명시적으로 전달
function UserCard({ name, email }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
    </div>
  );
}
3.불변성 관리

불변성을 철저히 지키는 것은 React 변경 감지 매커니즘이 올바르게 작동하는 핵심 !

// 중첩 객체 업데이트 시 올바른 불변성 유지 방법
function updateUserData(userId, newEmail) {
  // 좋지 않은 방식: 직접 수정
  const user = users.find(u => u.id === userId);
  user.email = newEmail; // 참조 변경 없음, React가 변화 감지 못함
  setUsers([...users]); // 배열 참조만 변경됨
  
  // 개선된 방식: 완전한 불변성 유지
  setUsers(users.map(user => 
    user.id === userId 
      ? {...user, email: newEmail} // 새 객체 생성
      : user
  ));
}

성능 최적화 시 주의점 : 더 이상은 과최적화

이 부분은 문득 ? 메모이제이션 자체에도 비용이 들지 않을까 해서 정리해본 내용입니다.

구현을 통해 배운 중요한 교훈은 모든 것을 메모이제이션하는 것이 항상 좋은 것은 아니라는 점입니다.

  1. 의존성 배열을 비교하는 비용
  2. 메모리에 이전 값을 저장하는 비용
  3. 자주 변경되는 데이터 -> 모든 렌더링에서 데이터가 변경된다면 의미가 있을까요?
useMemo 주의점

아래의 경우에만 useMemo를 사용하는 것이 적절하다고 판단됩니다.

  • 계산 비용이 실제로 많이 드는 작업 (복잡한 계산, 데이터 변환 등)
  • 렌더링마다 새로운 참조가 생성되는 객체/배열을 자식 컴포넌트에 전달할 때

�관련하여 오버헤드 실험하신 분의 인사이트 남기겠습니다.

Zustand와 같은 상태 관리 라이브러리의 메모이제이션 접근법

코치님이 첨부해주신 부분인데요, 같이 정리합니다.

import create from 'zustand';
import { shallow } from 'zustand/shallow';

// 스토어 생성
const useStore = create((set) => ({
  bears: 0,
  fish: 0,
  addBear: () => set(state => ({ bears: state.bears + 1 })),
  addFish: () => set(state => ({ fish: state.fish + 1 })),
}));

// 컴포넌트에서 사용
function BearCounter() {
  // 필요한 상태만 선택적으로 구독
  const bears = useStore(state => state.bears);
  return <h1>{bears} bears</h1>;
}

function FishCounter() {
  const fish = useStore(state => state.fish);
  return <h1>{fish} fish</h1>;
}

// 여러 상태 선택 시 얕은 비교 사용
function Controls() {
  // shallow를 사용하여 객체의 얕은 비교 수행
  const { addBear, addFish } = useStore(
    state => ({ addBear: state.addBear, addFish: state.addFish }),
    shallow
  );
  
  return (
    <>
      <button onClick={addBear}>Add bear</button>
      <button onClick={addFish}>Add fish</button>
    </>
  );
}
  1. 필요한 상태만 선택적으로 구독하여 불필요한 리렌더링 방지
  2. shadllow, deep 등 다양한 비교 함수 선택 가능
  3. selector 함수에 대한 자동 메모이제이션 제공

코드 품질

정리하면서 생각해보니 useCallback 부분을 useMemo를 리턴해도 괜찮았을 것 같다는 생각이 듭니다.

또, 깊은 비교에 대한 원리는 이해한 것 같으나, 실제로 어떤식으로 사용할지 가늠이 잡히지 않습니다.
과제를 진행하면서 모르는 부분을 더 챙기면서 했으면 좋았을 것 같다 생각이 듭니다

학습 효과 분석

React의 성능 최적화 Hooks를 직접 구현해보면서, 단순히 API를 사용하는 것을 넘어 그 내부 작동 원리와 최적화 전략에 대한 깊은 이해를 얻을 수 있었습니다.
특히 참조 동등성, 얕은 비교와 깊은 비교의 차이, 그리고 메모이제이션의 비용과 이점을 명확히 이해하는 것이 중요함을 배웠습니다.

실무에서는 이런 부분을 유념해서 렌더링을 최소화할 수 있을 것 같다는 생각이 들어서 자신감이 생기는 것 같습니다 : )

과제 피드백

이번 주차에는 셀프 체크리스트가 없었습니다 ! (제가 못찾는 걸까요..)
셀프 체크리스트는 과제 체크리스트와 다르게 명확히 알아야 하는 것들이 적혀있어서 너무 좋았습니다 : )
체크리스트에 답변들을 정리하면서 마무리할 수 있는 시간이 되는 것 같아서 너무 좋았습니다.

리뷰 받고 싶은 내용

  1. 이번 과제에서 깊은 비교를 사용할 경우가 있었을지 궁금합니다.!!
  2. 평소에 Ref 사용하면서 고민했던 부분들 정리했습니다. 특히 inputRef.current?.focus() 처럼 옵셔널 체이닝을 사용하는 방식에 대한 코치님의 의견이 궁금합니다. 실무에서는 어떤 방식으로 타입 안정성을 확보하시나요?
  3. 이력서 작성 관련하여 조언을 구합니다:
    개발자 이력서에 성과를 "수치화"하는 것이 중요하다고 들었는데, 이미 퇴사하여 정확한 데이터에 접근할 수 없는 경우 어떻게 경험을 효과적으로 표현할 수 있을까요?
    • 제 경험을 예를 들어보겠습니다.
      • 라이브 스트리밍 서비스의 시청화면 최적화한 경험이 있습니다.
      • 디버깅을 통해 화면 계산(디바이스, 다크모드, 레이아웃 다양성에 따른 계산) 조건 처리 부분이 병목 지점을 확인하였고 이를 개선하기 위해 필수 계산을 제외한 부분은 미디어 쿼리 접근 방식으로 변경하였습니다.
      • 부수적인 요소는 지연 로딩하는 작업을 진행했습니다 (화면, 채팅 먼저 다른 요소는 나중에!)
      • 또한, Styled-component는 런다임에 Js를 계산하고 주입하는 방식이라, Tailwind(정적 클래스 기반)으로 변경한 경험이 있습니다.
    • 당시에 수치 데이터를 기록을 해두지 않아서,, 성과로 표현하기 어려운 것 같습니다. 이러한 경험을 어떻게 효과적으로 표현할 수 있을지 코치님의 조언을 부탁드립니다.

여담

코치님, 4월 10일자로 2025년이 된 지 벌써 100일이 되었네요.
코치님은 100일동안 어떤 시간을 보내셨나요?

저는 요즘 관심있는 MCP에 대해 블로그 글("해커뉴스를 Obsidian에 정리해주는 MCP 적용기")을 써보기도 했습니다.
타팀 코칭 시간에 "누군가에게 설명하는 과정에서 가장 많은 것들을 배운게 된다" 라는 부분이 조금이나마 이해하게 된 것 같습니다.
무작정 쓰던 MCP를 블로그에 쓰려고하니, 개념도 공부해야 되더라고요.. 매우 미흡한 글이지만.. 많은 도움이 된 것 같아서 기쁩니다 :)

항해를 진행하면서 많은 것을 배우고, 그에 따른 자신감이 생겨서 올해 시작이 참 좋은 것 같습니다.

항상 많은 고민을 하고 만들어 주신 과제라는 생각이 드네요.
좋은 과제를 만들어 주셔서 감사드립니다 : )

Copy link

@keyonnaise keyonnaise left a 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에 관한 글입니다! 과제 정리하시면서 참고 삼아 보시면 좋을 것 같아 남겨놓습니다.

참고자료

Comment on lines +32 to +51
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;
}

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;

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()에 관한 글입니다. 참고하시면 좋을 것 같아서 남겨놓습니다.

hasOwnProperty를 사용하면 안되는 이유

Comment on lines +22 to +24
callback: T,
deps: DependencyList,
equals = shallowEquals

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오... useCallback()에 equals를 추가하신건 깊은 비교 처리를 위해서일까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쓰레기코드네요..당장 삭제

Comment on lines -2 to +15
// React의 useState를 이용해서 만들어보세요.
return { current: initialValue };
const [ref] = useState<{ current: T }>(() => {
return { current: initialValue };
});

return ref;

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] 처럼 한 줄로 작성할 수도 있어요!!

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에 대해서 고민이 많았어요. 남겨드리는 링크와 같은 방법도 있으니 관심이 있으시다면 한 번 참고해보시는 것도 좋을 것 같아요.

Comment on lines +1 to +10
/**
* 동등성 비교 함수
* 얕은 비교 수행
* 중첩된 객체나 배열은 참조 비교
*
* @template T - 비교할 값들 타입
* @param objA 1 비교
* @param objB 2 비교
* @returns 얕게 동등하면 true, 아니면 false
*/

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;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 넵 참조 동일성만 비교합니다~

Comment on lines +24 to +27
const shouldRender =
propsRef.current === null ||
!equals(propsRef.current, props) ||
Object.keys(propsRef.current).length !== Object.keys(props).length;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

별도로 렌더링 필요한 값을 할당해주니까 더 명확해지네요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants