Post

항해99 플러스 프론트엔드 5기 3주차

항해99 플러스 프론트엔드 5기 3주차

이번주 발제는 react의 기본 hook을 직접 구현하면서 어떤 원리로 동작하는지 파악해보기 입니다

shallowEquals

shallowEquals.ts 를 먼저 구현해야하는데

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const isObject = (obj: unknown): obj is Record<string, unknown> => {
  return obj !== null && typeof obj === "object";
};

export function shallowEquals<T>(objA: T, objB: T): boolean {
  if (objA === objB) {
    return true;
  }

  if (Array.isArray(objA) && Array.isArray(objB)) {
    if (objA.length !== objB.length) {
      return false;
    }
    return objA.every((value, key) => objB[key] === value);
  }

  if (isObject(objA) && isObject(objB)) {
    const entriesA = Object.entries(objA);
    const entriesB = Object.entries(objB);

    return (
      entriesA.length === entriesB.length &&
      entriesA.every(([key, value]) => {
        return objB[key] === value;
      })
    );
  }
  return false;
}

isObject 타입 가드로, null 방지랑 비교를 해주고
objA === objB return true 같은 값이면 바로 통과
Array 비교시 every로 순서대로 모든 요소 비교
Object 비교시, key/value pair를 비교

deepEquals

이건 이제 깊은 비교인데요, 발제때 shallowEquals에서 조금만 바꾸면 된다는 힌트를 주셨고, deepEquals 자체가 iterative하게 모든 내부 값을 비교하는것 이였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const isObject = (obj: unknown): obj is Record<string, unknown> => {
  return obj !== null && typeof obj === "object";
};

export function deepEquals<T>(objA: T, objB: T): boolean {
  if (objA === objB) {
    return true;
  }

  if (Array.isArray(objA) && Array.isArray(objB)) {
    if (objA.length !== objB.length) {
      return false;
    }
    return objA.every((value, index) => deepEquals(value, objB[index]));
  }

  if (isObject(objA) && isObject(objB)) {
    const keysA = Object.keys(objA);
    const keysB = Object.keys(objB);
    if (keysA.length !== keysB.length) return false;
    return keysA.every((key) => deepEquals(objA[key], objB[key]));
  }
  return false;
}

iterative 하게 진행될수있게 deepEquals를 안에다가 넣어줍니다.

useRef

발제 때 주신 답으로 진행했습니다

1
2
3
4
5
6
7
import {useState} from "react";

export function useRef<T>(initialValue: T): { current: T } {
  // React의 useState를 이용해서 만들어보세요.
  const [value] = useState({current: initialValue});
  return value;
}

useRef는 rerender를 유발하지 않으면서 .current를 가진 object를 return 합니다.

useState는 최초 1회만 초기값 함수 또는 값을 실행하는데요 [value]로 만 가져오고 setValue는 쓰지 않아 값은 유지, { current: T } object는 rendering간에 동일한 참조로 유지되어 .current를 수정해도 rerendering이 발생하지 않습니다.

useMemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {DependencyList} from "react";
import {shallowEquals} from "../equalities";
import {useRef} from "./useRef.ts";

export function useMemo<T>(
  factory: () => T,
  _deps: DependencyList,
  _equals = shallowEquals,
): T {
  // 직접 작성한 useRef를 통해서 만들어보세요.

  const memoRef = useRef<{ _deps: DependencyList; value: T } | null>(null);

  if (memoRef.current === null || !_equals(memoRef.current._deps, _deps)) {
    memoRef.current = {
      _deps: _deps,
      value: factory(),
    };
  }
  return memoRef.current.value;
}

memoRef를 통해 useRef{ _deps, value } 구조로 이전 값을 기억, dependent array가 바뀌었을때만, factory()를 실행합니다!

This post is licensed under CC BY 4.0 by the author.