TIL-2024/09/10

박상우·2024년 9월 10일
0

매일 면접 준비

Flutter 프로젝트에서 상태관리 도구를 사용하셨던데 어떤 도구를 사용하셨고, 상태관리가 왜 필요한가요?

→ Flutter 프로젝트를 진행하면서 Getx 라는 전역 상태 툴을 활용하였습니다. 기본적으로 상태에 대한 UI의 변화는 StatefulWidget 내부에서 수행되지만, 여러서 위젯 내부에서 상태 변화를 고려해야하는 경우, Getx의 Rx 변수를 선언하여 전역 상태관리를 할 수 있습니다. 그리고 Rx 변수들은 controller로 선언된 함수들에 의해서 상태를 변화시키고 관리할 수 있습니다.

React 프로젝트에서도 전역 상태 관리 도구를 사용하셨는데, 두 도구는 어떤 차이가 있었나요?

→ React 프로젝트에서는 Zustand를 사용해 전역 상태 관리를 했습니다. React에서는 컴포넌트 간에 상태를 전달할 때 props drilling 문제가 발생할 수 있는데, 이 문제를 해결하기 위해 Store라는 중앙 저장소 개념을 도입한 상태 관리 도구들을 사용합니다.

→ Zustand는 중앙 집중식 Store를 사용하여 여러 컴포넌트가 공유하는 상태와 이를 조작하는 action들을 한 곳에 모아 관리합니다. 반면, GetX는 특정 상태를 전역으로 선언한 Rx 변수와 Controller를 통해 필요할 때마다 각 컴포넌트에서 상태를 관리하고 반응형으로 UI를 업데이트하는 방식입니다. Zustand는 중앙 집중화된 관리 방식인 반면, GetX는 보다 분산된 방식으로 각각의 컴포넌트에서 필요한 상태만 관리하는 구조입니다.


useEffect 콜백함수에 비동기 함수를 사용할 수 없는 이유

useEffect 훅의 타입 정의는 아래와 같다.

function useEffect(effect: EffectCallBack, deps?: DependencyList): void;
type DependencyList = ReadonlyArray<any>;
type EffectCallback = () => void | Destructor;

useEffect에서 EffectCallBack 함수 타입을 가지는 effect 인자는 아무것도 반환하지 않거나 Destructor를 반환한다고 한다.

만약 effectCallBack 함수가 비동기로 쓰이게 되면, 콜백함수는 Promise를 return하게 된다. 기본적으로 Promise 타입의 함수는 반환할 수 없고, 허용했을 때 race condition의 가능성 때문에 동기 함수를 사용하는 것을 원칙으로 정했다.

만약 내부적으로 비동기로직을 활용해야한다면 아래와 같이 비동기 함수를 호출하여 useEffect 콜백 자체는 동기를 유지할 수 있도록 해야한다.

useEffect(() => {
	const fetchAsync = async () => {
		...
		const res = await someAsyncFunction();
		...
	}
	
	fetchAsync();
}, []}

useEffect return 함수에 대한 오해

useEffect(() => {
	...
	
	return () => {}
}, []);

useEffect 내부에서 함수를 return 할 수 있다. 이 함수는 컴포넌트 해제 전에 정리 작업을 수행하기 위한 함수로, 컴포넌트가 마운트 해제될 때만 실행된다고만 알고 있었었다. 의존성 배열이 존재하면, 의존서 배열의 값이 변경 될 때마다 실행된다고 한다.


useRef 사용

useRef의 제네릭에 몇가지 다양한 케이스가 들어갈 수 있는데 그 중 내가 자주 쓰는 두 표현식에 어떤 차이가 있는지 알게되어서 정리를 한다.

아래 두 방식인데,

const componentRef1 = useRef<HTMLDivElement | null>()
const componentRef2 = useRef<HTMLDivElement>()

내가 사용할 당시에는 단순하게 ref가 참조하는 값에 대해서 null을 고려하는지 여부에 따라 사용법을 구분했었다. 하지만 두 방식에는 내가 생각하지 못한 차이가 있었다.

실제 useRef를 정의한 코드를 보면 여러 형태 중 아래와 같은 두가지 형태를 볼 수 있다.

function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T | null): RefObject<T>;

눈에 띄는 차이는 return하는 객체의 타입이 다르다는 점이다.

만약 제네릭에 HTMLDivElement | null 타입을 사용한 경우 useRef는 첫번째 케이스에 해당하고 MutableRefObject 타입을 따른다.
Mutable하다는 단어에 맞게 해당 타입은 current를 변경할 수 있는 값이 되고, ref.current의 값을 조작하거나 바뀔 수 있다.
RefObject 는 반대로 current의 값을 임의로 조작할 수 없다.

처음 이부분을 알게 되었을 때, current 자체를 바꿀 수 있다. or 없다. 로 구분했었다. 하지만 그것보다 제네릭의 타입에 의해서 리턴하는 객체의 차이가 생긴 것 같다는 생각을 하게 되었다. 위의 상단의 예시에서도 HTMLDivElement | null 을 제네릭으로 넘겨주는 경우 useRef는 두 타입 중 하나의 타입에 해당하는 값을 가지게 되기 때문에 어느 한 쪽으로 변경이 가능해야 할 것이다. 반대로 HTMLDivElement 단일 타입으로 선언한 경우 해당 타입 외에 다른 타입이 할당 되지 않을 것이기 때문에 ReadOnlyRefObject로 리턴해준 것 같다.

useRef의 초기 값을 null로 설정하는 이유

  • useRef에서 초기값을 null로 설정하는 이유는 주로 초기 렌더링 시점에는 참조할 DOM 요소가 없기 때문에 이를 명시적으로 표현하고, 타입 안정성을 유지하며, 이후 렌더링 완료 후 DOM 요소를 참조하도록 하기 위함이라고 한다.

useRef의 다른 특징

useRef를 자식 컴포넌트 뿐만 아니라 다른 값을 저장해서 사용할 수 도 있다.

const isLoading = useRef(false);

일반적으로 컴포넌트 내부에서 useState를 통한 상태를 활용하는데, state는 일반적으로 변경 함수 호출 이후 랜더링이 완료된 시점에서 변경된 값을 확인할 수 있다.

반면 useRef의 경우 값이 변경된 즉시 변경된 값으로 확인가능 하다.

랜더링에 영향을 주지않는 값이고, 즉시 변경 여부를 확인해야하는 경우에 useState 대신 useRef를 써보는 것도 좋을 것 같다.

profile
나도 잘하고 싶다..!

0개의 댓글

관련 채용 정보