- 직장에서 코드리뷰(라고 불리기에는 한 달에 한 번)시간에 공유한 내용을 다시 정리한 글입니다.
- 23년 9월, 23년 10월의 내용에 공통되는 부분이 있어서 하나로 합쳤습니다.
- UI는 Kendo UI를 활용합니다.
- React Fiber를 아직 보지 않았습니다. 다 발표하고 나서야 발견을 했습니다. 따라서 이번에는 React Fiber에 대한 내용을 포함하지 않았습니다.
최근 2달간 회사에서 진행한 코드리뷰의 주제는 다음 2개다.
이 두 주제의 핵심은 "렌더링 최적화"였다.
// before
const Component = () => {
const [ dependecy, setDependecy ] = useState()
const InputCell = () => {
};
return(
<Field component={InputCell} />
)
}
TS 전환 및 리팩터링을 진행하는 와중에 위와 같은 페이지 컴포넌트를 만났다.
InputCell은 내부에 선언이 되어있고 focus값이 탈출을 하는 상황을 인지하고, 탈출을 막기 위해서 ref.current
로 foucs를 유지를 하고 있었다고 한다.
해당 페이지를 TS로 전환하면서 최신화를 하기위해서 현상을 검색하다가 useCallback을 활용하면 focus가 풀리는 것을 막을 수 있다고 한다.
따라서 아래와 같이 수정을 하였고, 기존 ref.current
를 활용하지 않고도 foucs를 유지할 수 있게 되었다.
// after
const Component = () => {
const [ dependecy, setDependecy ] = useState()
const InputCell = useCallback(() => {
}, [dependecy])
return(
<Field component={InputCell} />
)
}
상태만으로 관리하던 input값을 Kendo의 Form으로 전환시키는 작업을 했다.
모든 값을 선언해주고 api연결을 위해서 submit action 실행 시 input의 값들이 초기값으로 롤백이 되는 현상이 발생했다.
이 때는 위 1차 상황을 해결 한 이후 였기에 Form의 initalValues api에 할당하는 함수를 useCallback으로 감싸 주었다.
(Tanstack query의 select를 useCallback으로 래핑)
// useLpConfiguration 내부
export function useLpConfiguration({ queryKey }: { queryKey: CustomQueryKey }) {
const { data: res, error, isSuccess } = useQuery(queryKey, Policy.getLpConfiguration, {
select: useCallback(({ data }: { data: LpItem }) => convertSetLpData(data), [])
});
return {
lpData: res,
error,
isSuccess,
};
}
// 화면 로직
const { error, lpData } = useLpConfiguration({ queryKey: [QUERY_KEY.lp] });
const initialValues = { ...lpData };
return (
<Form
initalValues={initalValues} // 초기값
key={JSON.stringify(initalValues)} // 리렌더링 필요 여부의 판단값
render=(.....)
/>
)
두 상황 모두 렌더링 최적화를 하지 않았기 발생한 상황이다.
먼저 두 상황 모두 리액트로 인하여 새로운 html로 계속 마운트되었기 때문이다.
그렇기에 내가 원하는 부분을 그대로 유지를 하기 위해서는 최적화 작업을 하여, 해당 부분이 특정 상황에서만 마운트 되게 해줘야 했다.
이런 부분은...
근데 왜 이렇게 해줘야만 할까? 그냥 작성하면 되지 않았을 까?
결국 위와 같이 해야 하는 이유는 리액트의 렌더링 과정을 보고 이해할 수 있었다.
이 기회가 없었다면 최적화에 대한 이유를 찾지도 않았을 것 같다.
최적화가 필요한 이유를 알기 위해선 리액트의 동작방식에 대한 이해가 필요했다.
공식문서 및 몇몇 개발 글들을 조합했을 떄 부가설명(내부 동작 원리) 없이 간단하게 요약하자면 다음과 같이 정리를 할 수 있었다.
여기서 내가 확인해야할 부분은 2번과 3번이었다.
내가 겪은 문제들은 다음 조건으로 인해 리렌더링 조건을 만족했다.
따라서,
1. 화면 파일이 다시 재평가되면서 컴포넌트 본문에 선언된 모든 변수는(Kendo Input) 다른 함수라고 인식을 하기에 화면에 다시 업데이트 필요라고 인식
2. 제출 시, 함수가 다시 재평가되면서 key로 가지고 있는 query의 data가 변경되면서 이전과 다르다고 인식하여 화면 업데이트
결국 나는 내 상황에서 해당 값들은 재평가 될 때 특정 상황이 아닌 경우 이전과 같은 값이라고 React에게 전달해줘야 했다.
처음 이해가 되지 않는 질문이었다. 그래서 다시 의도를 여쭤보니 "window 객체에 붙어 있는 event들과 virtual DOM이 기존 tree를 끊어내고 다른 tree를 붙였을 때 기존 tree에 대한 메모리 삭제를 진행하는지"에 대해서 궁금하셨다.
솔직하게 모른다고 했다.
시니어 개발자분의 따르면 dom자체가 최적화되기 어렵고 JavaScript의 garbage collector또한 성능이 타 언어에 비해 좋지 않다고 한다. 그럼에도 살아남아 계속 사용하게 되는 이유는 "우리가 브라우저가 느리다고 인식이 되면 새로고침을 통해서 메모리를 초기화시키는 행동"을 하는 것이 하나의 원인이 될 수 있다고 한다.
또한, event들이 전역 객체에 붙여 있기에 event listener 떼어주는 행위도 필요하다고 한다.
지금까지 수많은 글에서 useEffect 사용 시 return문에 event를 왜 해지하는 지 몰랐는데, 이 질의를 통해서 그 코드의 사용성을 이해했다.
처음 개발 공부할 때 생명주기를 알려주는데 class기반의 내용이고 hook이 이것을 함수에서 할 수 있도록 도와준다고 하는데, 이해를 해본 적이 없었다. 왜냐하면 함수기반 생명주기가 class의 모든 것과 부합하지 않았기 떄문이다.
그러나 이번 기회에 어떻게 함수기반 컴포넌트의 렌더링을 파악하고 최적화가 필요한 상황이 왜 생겼는지를 알게되었다.
이후 블로그로 다시 글을 옮기게 되면서 내가 위에서 공부한 내용이 예전 내용이라는 것을 알게되었다.
현재는 Fiber라는 새로운 알고리즘을 채택하여 리액트를 변화시켰다고 한다.
다음 공부해야 할 것에 Fiber도 추가해야겠다. 또한, 이번 2달간의 발표로 브라우저에 대한 공부도 더 필요하다는 생각이 들었다.