이번 주차는 raect의 다양한 hooks들을 만들고 사용해보며 동작원리를 체득해보는 시간이었다.

useRef, useMemo, useCallback, memo를 직접 구현해보면서 메모이제이션에 대해 더 깊게 생각해볼 수 있었고,

useContext로 랜더링을 최적화 하는 과정을 통해 보다 응집도 있는 코드 작성하는 법에 대해 배울수 있었다.

 

1. useRef

렌더링 사이에 값을 유지하는 ref 객체를 생성

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

 

처음엔 아래처럼 object에 값을 넣어도 동일하게 동작할 줄 알았다

const ref = { currnet: initialValue };

그런데 이렇게하면 렌더링이 될때마다 값이 초기화돼버렸다.

새로고침 하기 전까지는 값을 유지하는 useState 내부 저장소에 값을 저장했다

useState에 저장한 값을 변경하는 방법으로 리랜더링하지 않고 랜더링 사이에도 값을 유지하는 useRef를 구현했다. 

ref.current = "값 변경";

 

 


 

그런데 useRef를 구현하고보니 useState에 궁금증이 생겼다.

useState는 어디에 값을 저장하길래 object처럼 초기화되지 않는거지? 

 

React는 특정 공간에 state 값을 저장하고있다.

공식문서에서도 일반 변수는 랜더링하면 값 초기화되니까 렌더링에 사용할 변수는 useState를 사용하라고 강조하고 있다 (https://react.dev/learn/state-a-components-memory#)

 

클로저

React는 state의 값을 저장하기위해 클로저를 사용하고있다.

클로저를 통해 useState가 호출될 때마다 초기화되지 않고 기존 상태를 유지할 수 있게된다.

// 클로저를 통해 useState 구현

const createUseState = (initialValue = 0) => {
  let value = initialValue; // 클로저로 상태를 저장

  const state = () => value; // 상태를 반환하는 함수
  const setState = (newValue) => {
    value = newValue; // 상태를 업데이트
  };

  return [state, setState]; // 반환
};

const customUseState = createUseState(0); // 초기 상태를 0으로 설정
export default customUseState;
// 컴포넌트에서 사용

const [state, setState] = customUseState;
  const onClick = () => {
    setState(state() + 1);
    console.log(state());
  };

 

 

 

useState를 비슷하게 만들어보니 호출할 때마다 useState를 구분해줘야되는 문제점이 있었다.

리액트는 어떻게 해결했나 공식문서를 보니 역시 해답이 나와있었다.

  

useState는 각각의 useState 인덱스를 key 값으로 구분하고있다.

let componentHooks = [];
let currentHookIndex = 0;

function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
	// 이미 호출된 useState 쌍이면 return 해서 호출 대기
	currentHookIndex++;
    return pair;
  }

  // 처음 호출된 useState 는 우리가 흔히 사용하는 [state, setState] 쌍을 만든다
  pair = [initialState, setState];

  function setState(nextState) {
    // setState가 변경되면 state 값을 변경하고 DOM을 update한다.
    pair[0] = nextState;
    updateDOM();
  }

  // 이후 렌더링을 위해 쌍을 저장하고 다른 useState 호출 대기를 위해 인덱스 +1
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

 

2. useMemo

react는 useCallback, useMemo, memo 등의 의존성을 판단할 때 얕은 비교를 한다.

만약 깊은 비교를 통해 위 hook들을 사용하고 싶다면 별도의 커스텀 훅을 만들어 진행해야한다.

export function useMemo<T>(
  factory: () => T,
  _deps: DependencyList,
  _equals = shallowEquals,
): T {
  const prevDeps = useRef<null | DependencyList>(null);
  const prevFactory = useRef<null | T>(null);

  const isEqual = shallowEquals(prevDeps.current, _deps); // 얕은 비교
  if (!isEqual) {
    prevDeps.current = _deps;
    // 참조값 _deps 가 변경되면 factory함수를 다시 생성한다.
    prevFactory.current = factory();
  }

  return prevFactory.current as T;
}

 

3. useCallback

usememo는 값을 useCallback은 함수를 메모이제이션하므로 아래와 같이 간단하게 구현 가능하다. 

export function useCallback<T extends Function>(
  factory: T,
  _deps: DependencyList,
) {
  const _factory = useMemo(() => factory, _deps);

  return _factory as T;
}

 

4. memo

memo도 useMemo, useCallback과 동일하게 얕은비교를 하는데 react element를 다시 생성해줘야한다.

이때 넘겨준 props와 children을 return 하는 createElement라는 react 메서드를 사용한다.

export function memo<P extends object>(
  Component: ComponentType<P>,
  _equals = shallowEquals,
) {
  return function MemoizedComponent(props: P) {
    const prevProps = useRef<P | null>(null);
    const prevComponent = useRef<ReactElement | null>(null);

    const isEqual = shallowEquals(prevProps.current, props);
    if (!isEqual) {
      prevProps.current = props;
      prevComponent.current = createElement(Component, props); element 재생성
    }

    return prevComponent.current;
  };
}

 

전 기수에게 지급되는 할인 코드 2CFT40

 

항해플러스에 합류하실 때 요 할인코드(2CFT40) 를 입력하면 20만원 할인혜택이 있습니다!

4/30일 까지는 프로모션 기간으로 30만원 할인되니 합류하시려면 서두르세요 🔥🔥

저에게도 혜택이 있으니 제 할인코드를 적용하신 분은 댓글 남겨주시면 제가받은 혜택 절반을 페이백 해드리겠습니다🎉 

 

합류하러 가기🦖

+ Recent posts